![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