![Advanced-UI](https://img.shields.io/static/v1?label=UI&message=Advanced&color=lightgrey)
The SCIM Endpoint plugin is an API for managing users and the groups in CELUM. The plugin implements version 2.0 of the
SCIM protocol.
* [System for Cross-domain Identity Management: Core Schema](https://tools.ietf.org/html/rfc7643)
* [System for Cross-domain Identity Management: Protocol](https://tools.ietf.org/html/rfc7644)
[MINITOC]
## Properties
To be configured in {home}/appserver/conf/custom.properties
##### scimEndpoint.license
> type: String, **required: yes**, default: -
The license key for the plugin (product: scimEndpoint), provided by brix.
##### scimEndpoint.tokenVerifier
> type: bean name, **required: yes**, default: -
Determines which Authentication method is applied (scimStaticTokenVerifier or scimJsonWebTokenVerifier).
##### scimEndpoint.staticToken
> type: String, **required: yes (if applied)**, default: -
Simple static token for scimStaticTokenVerifier.
##### scimEndpoint.jwtSecretKey
> type: String, **required: yes (if applied)**, default: -
The secret key for scimJsonWebTokenVerifier (at least 256 bits!)
##### scimEndpoint.delete
> type: boolean, required: no, default: false
Determines whether a user is deleted or deactivated in case of a DELETE request.
##### scimEndpoint.notVisibleUser
> type: List of long (comma-separated), required: no, default: -
A list of users who will be excluded from CRUD operations.
##### scimEndpoint.notDeletableUser
> type: List of long (comma-separated), required: no, default: -
A list of users who are not deletable.
##### scimEndpoint.userKindDefault
> type: String, required: no, default: readonly
Sets the userKind if no userType is transmitted.
##### scimEndpoint.notVisibleGroup
> type: List of long (comma-separated), required: no, default: -
A list of groups who will be excluded from CRUD operations.
##### scimEndpoint.notDeletableGroup
> type: List of long (comma-separated), required: no, default: -
A list of groups who are not deletable.
## Request Header
To access the SCIM API a **Bearer Token** is required. The API token must be included via an *
*Authentication/Authorization header** with a type of Bearer when calling any of the SCIM methods.
Alternatively, a static token can be provided in the token parameter.
The http **Content-Type** header has to be set to **application/scim+json**.
## SCIM Resource Endpoints
The base URL for all calls to the SCIM API is https://example.com/scim/v2. All SCIM methods are branches of this base
URL.
The following Endpoints are available:
### Service Provider Configuration Endpoints
**GET /ServiceProviderConfig**
* Returns the SCIM specification features.
**GET /ResourceTypes**
* Returns the types of resources available.
**GET /Schemas**
* Returns information about resource schemas.
### User
**GET POST PUT PATCH DELETE /Users**
* For GET requests a standard set of query parameters is available (
see [Query Resources](https://tools.ietf.org/html/rfc7644#section-3.4.2)).
* Since the ID is of type string, sorting to this attribute is limited.
* Since Celum does not have a lastModified date for users, the created date is returned in meta.lastModified.
#### User attributes
The following table maps SCIM attributes to CELUM User attributes.
| CELUM | SCIM | Remarks |
|-------------|----------------------------------------------|-------------------------------------------------------------------------------------------------|
| ID | id | required |
| External ID | externalId | |
| Username | userName | required |
| Password | password | |
| Last name | name.familyName | |
| First name | name.givenName | |
| Middle name | name.middleName | |
| Deactivated | active | Default value for POST is active. |
| Userkind | userType | Valid values are readonly, editor or readwrite. Defaul value is readonly and can be configured. |
| E-mail | emails[value][primary][type=work] | |
| Phone | phoneNumbers[value][type=work] | type is required |
| Mobile | phoneNumbers[value][type=mobile] | type is required |
| Fax | phoneNumbers[value][type=fax] | type is required |
| Street | addresses[streetAddress][primary][type=work] | |
| Zip | addresses[postalCode][primary][type=work] | |
| City | addresses[locality][primary][type=work] | |
| Country | addresses[country][primary][type=work] | |
| User Groups | groups | Group membership changes MUST be applied via the "Group" Resource |
| Created | meta.created | |
GET /Schemas/urn:ietf:params:scim:schemas:core:2.0:User returns all user attributes and their characteristics that
describe their type and handling.
All attributes must be transmitted in a PUT request. Not required attributes that are missing or empty are usually
deleted in Celum. Exceptions:
* Password: will not be deleted
* Deactivated and Userkind: no changes
If multiple emails/addresses are transmitted, one must be marked as primary. Otherwise, no one will be stored in Celum
in a POST request and no updated is done in a PUT request.
#### Example for GET
**Full User Representation**
```json
{
"id": "170",
"externalId": "externalID",
"userName": "Test_User_1",
"name": {
"familyName": "Mustermann",
"givenName": "Max",
"middleName": "D."
},
"active": true,
"emails": [
{
"value": "m.mustermann@email.ch",
"type": "work"
}
],
"phoneNumbers": [
{
"value": "061 266 66 66",
"type": "work"
},
{
"value": "079 266 66 66",
"type": "mobile"
},
{
"value": "061 266 66 11",
"type": "fax"
}
],
"addresses": [
{
"streetAddress": "Musterstrasse 1",
"locality": "Binningen",
"postalCode": "4102",
"country": "Schweiz",
"type": "work"
}
],
"groups": [
{
"value": "149",
"$ref": "http://localhost:8881/scim/v2/Groups/149",
"display": "Admin_Group",
"type": "Group"
},
{
"value": "7",
"$ref": "http://localhost:8881/scim/v2/Groups/7",
"display": "Marketing",
"type": "Group"
},
{
"value": "126",
"$ref": "http://localhost:8881/scim/v2/Groups/126",
"display": "Test_Group_1",
"type": "Group"
}
],
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"meta": {
"resourceType": "User",
"created": "2021-04-14T13:45:31.787Z",
"lastModified": "2021-04-14T13:45:31.787Z",
"location": "http://localhost:8881/scim/v2/Users/170"
}
}
```
#### Example for POST (create new user)
**Full User Representation**
```json
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"externalId": "externalID",
"userName": "Test_User_1",
"password": "123456789A!",
"name": {
"familyName": "Mustermann",
"givenName": "Max",
"middleName": "D."
},
"active": true,
"userType": "editor",
"emails": [
{
"value": "m.mustermann@email.ch",
"primary": true
},
{
"value": "m.muster@email.ch"
}
],
"phoneNumbers": [
{
"value": "061 266 66 66",
"type": "work"
},
{
"value": "061 266 66 11",
"type": "fax"
},
{
"value": "079 266 66 66",
"type": "mobile"
}
],
"addresses": [
{
"streetAddress": "Musterstrasse 1",
"locality": "Binningen",
"postalCode": "4102",
"country": "Schweiz",
"primary": true
},
{
"streetAddress": "Musterstrasse 2",
"locality": "Binningen",
"postalCode": "4102",
"country": "Schweiz"
}
]
}
```
**Minimal User Representation**
```json
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"userName": "Test_User_1",
"name": {
"familyName": "Mustermann"
}
}
```
### Group
**GET POST PUT PATCH DELETE /Groups**
* For GET requests a standard set of query parameters is available (
see [Query Resources](https://tools.ietf.org/html/rfc7644#section-3.4.2)).
* Since the ID is of type string, sorting to this attribute is limited.
* Since Celum does not have a created or lastModified date for groups, "1970-01-01T00:00:00.000Z" is returned in
meta.created and meta.lastModified.
#### Group attributes
The following table maps SCIM attributes to CELUM Usergroup attributes.
| CELUM | SCIM | Remarks |
|---------------|------------------|----------|
| ID | id | reqiured |
| External ID | externalId | |
| Name | displayName | required |
| ID | members[value] | |
| Username/Name | members[display] | |
| Discriminator | members[type] | |
GET /Schemas/urn:ietf:params:scim:schemas:core:2.0:Group returns all group attributes and their characteristics that
describe their type and handling.
#### Example for GET (full usergroup representation)
```json
{
"id": "186",
"externalId": "externalID",
"displayName": "Test_Group_2",
"members": [
{
"value": "40",
"$ref": "http://localhost:8881/scim/v2/Users/40",
"display": "editor2",
"type": "User"
},
{
"value": "43",
"$ref": "http://localhost:8881/scim/v2/Users/43",
"display": "editor3",
"type": "User"
},
{
"value": "44",
"$ref": "http://localhost:8881/scim/v2/Users/44",
"display": "readonly3",
"type": "User"
},
{
"value": "8",
"$ref": "http://localhost:8881/scim/v2/Groups/8",
"display": "World",
"type": "Group"
}
],
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:Group"
],
"meta": {
"resourceType": "Group",
"created": "1970-01-01T00:00:00.000Z",
"lastModified": "1970-01-01T00:00:00.000Z",
"location": "http://localhost:8881/scim/v2/Groups/186"
}
}
```
#### Example for POST (create new usergroup)
**Full Usergroup Representation**
```json
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:Group"
],
"externalId": "externalID",
"displayName": "Test_Group_2",
"members": [
{
"value": "40",
"type": "User"
},
{
"value": "42",
"type": "User"
},
{
"value": "7",
"type": "Group"
}
]
}
```
**Minimal Usergroup Representation**
```json
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:Group"
],
"displayName": "Test_Group_3"
}
```
### Further Endpoints
**POST /Bulk**
* The SCIM bulk operation enables clients to send a potentially large collection of resource operations in a single
request.
**POST [prefix]/.search**
* Clients MAY execute queries without passing parameters on the URL by using the HTTP POST verb combined with the "
/.search" path extension.
## Implementations
### Azure
1. Create an Enterprise Application: _Azure Active Directory --> Enterprise applications --> Create your own
application --> Non-gallery_
2. Set Admin Credentials: _Application --> Provisioning --> Update credentials --> Admin Credentials_
* Provisioning Mode = Automatic
* Tenant URL (https://example.com/scim/v2)
* Secret Token (provided by brix)
3. Adapt mappings: _Application --> Provisioning --> Edit attribute mappings --> Mappings_
* See mapping tables below
* Configure the settings as needed
* Change customappsso attributes: _Show advanced options --> Edit attribute list for customappsso_
* Change mappings
* Delete all unused customappsso attributes and mappings.
> > > The mapping must be deleted before you can edit/delete the customappsso attributes.
4. Set Scope (if needed): _Application --> Provisioning --> Add scoping filters --> Settings --> Scope_
* Sync only assigned users and groups
5. Assign users/groups (if needed): _Application --> Users and groups --> Add user/groups_ (groups only available in
enterprise/premium azure)
#### User Mapping
| Azure Active Directory Attribute | customappsso Attribute | Type | Remarks |
|-------------------------------------------------------------|:----------------------------------------|:--------|:----------------------|
| - | id | String | primary key, required |
| userPrincipalName | userName | String | required |
| Switch([IsSoftDeleted], , "False", "True", "True", "False") | active | Boolean | |
| mail | emails[type eq "work"].value | String | |
| givenName | name.givenName | String | |
| surname | name.familyName | String | |
| streetAddress | addresses[type eq "work"].streetAddress | String | |
| city | addresses[type eq "work"].locality | String | |
| telephoneNumber | phoneNumbers[type eq "work"].value | String | |
| mobile | phoneNumbers[type eq "mobile"].value | String | |
| facsimileTelephoneNumber | phoneNumbers[type eq "fax"].value | String | |
| mailNickname | externalId | String | |
| postalCode | addresses[type eq "work"].postalCode | String | |
| country | addresses[type eq "work"].country | String | |
#### Group Mapping
No changes need to be made to the default mapping for groups.
| Azure Active Directory Attribute | customappsso Attribute | Type | Remarks |
|----------------------------------|:-----------------------|:----------|:-------------------------------------------------------------------------------------------------------------|
| - | id | String | primary key, required |
| objectId | externalId | String | |
| displayName | displayName | String | required |
| members | members | Reference | urn:ietf:params:scim:schemas:core:2.0:Group
urn:ietf:params:scim:schemas:extension:enterprise:2.0:User |
#### Known Limitations
see [Link](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/application-provisioning-config-problem-scim-compatibility)
**Tenant URL**
Use the flags below in the tenant URL of your application in order to change the default SCIM client behavior.
`/?aadOptscim062020` (e.g. https://example.com/scim/v2/?aadOptscim062020)
**Null attribute can't be provisioned**
Azure AD currently can't provision null attributes. If an attribute is null on the user object, it will be
skipped ([Link](https://docs.microsoft.com/en-us/azure/active-directory/app-provisioning/known-issues#null-attribute-cant-be-provisioned)).
**Remove user from synced group**
If a user is no longer in a synced group, Azure will not send a DELETE, but only a PATCH with active = false. Thus, the
user is only deactivated in
Celum ([Link](https://docs.microsoft.com/en-us/azure/active-directory/app-provisioning/customize-application-attributes#:~:text=The%20attribute%20IsSoftDeleted,your%20attribute%20mappings.))
**Delete user**
When a user is deleted, Azure apparently only softDelets it first. This leads to a PATCH/PUT with active =
false ([Link](https://stackoverflow.com/questions/42799936/azuread-scim-integration-not-sending-delete-requests#:~:text=If%20a%20user,DELETE%20the%20user.)).
Here you have to delete the user permanently so that Azure sends a DELETE request.
**userName softDeletion**
Azure prefixes the userName with the ObjectID during softDeletion. This can lead to the userName being too long for
Celum, which is why the user cannot be deactivated.
## Compatibility Matrix
| SCIM Endpoint | CELUM (min. version) |
|---------------|------------------------|
| 1.0 | 6.4.0 |
| 1.0.3 | 6.4 (tested with 6.11) |
| 1.2.2 | 6.4 (tested with 6.11) |
## Release Notes
#### 1.0.0
> Release: 2021-04-23
Initial Version
#### 1.0.3
> Release: 2022-01-28
* default value for lastName
* changed name.familyName to not required
* changed member.type to not required
#### 1.2
> Release: 2024-03-07
added type 'work' to emails and addresses