Update to Keycloack 26 and scim-dsk 1.26
This commit is contained in:
parent
7d87d23345
commit
e8177237e0
7 changed files with 67 additions and 44 deletions
80
README.md
80
README.md
|
@ -1,35 +1,59 @@
|
||||||
# keycloak-scim-client
|
# keycloak-scim-client
|
||||||
|
|
||||||
This extension add [SCIM2](http://www.simplecloud.info) client capabilities to Keycloak. (See [RFC7643](https://datatracker.ietf.org/doc/html/rfc7643) and [RFC7644](https://datatracker.ietf.org/doc/html/rfc7644)).
|
This extension add [SCIM2](http://www.simplecloud.info) client capabilities to Keycloak.
|
||||||
|
|
||||||
|
It allows to :
|
||||||
|
|
||||||
|
* Declare SCIM endpoints (through the identity federation UI). Any tool implementing SCIM protocol can be wired to the
|
||||||
|
Keycloack instance through this declaration.
|
||||||
|
* Propagate users and groups from Keycloack to SCIM endpoints : when a user/group gets created or modified in Keycloack,
|
||||||
|
the modification is fowarded to all declared SCIM endpoints through SCIM calls within the transaction scope. If
|
||||||
|
propagation fails, changes can be rolled back or not according to a configurable rollback strategy.
|
||||||
|
* Import users and groups from SCIM endpoints (through the Keycloack synchronization mechanism).
|
||||||
|
|
||||||
|
See [RFC7643](https://datatracker.ietf.org/doc/html/rfc7643)
|
||||||
|
and [RFC7644](https://datatracker.ietf.org/doc/html/rfc7644)) for further details
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
### Motivation
|
### Motivation
|
||||||
|
|
||||||
We want to build a unified collaborative platform based on multiple applications. To do that, we need a way to propagate immediately changes made in Keycloak to all these applications. And we want to keep using OIDC or SAML as the authentication protocol.
|
We want to build a unified collaborative platform based on multiple applications. To do that, we need a way to propagate
|
||||||
|
immediately changes made in Keycloak to all these applications. And we want to keep using OIDC or SAML as the
|
||||||
|
authentication protocol.
|
||||||
|
|
||||||
This will allow users to collaborate seamlessly across the platform without requiring every user to have connected once to each application. This will also ease GDRP compliance because deleting a user in Keycloak will delete the user from every app.
|
This will allow users to collaborate seamlessly across the platform without requiring every user to have connected once
|
||||||
|
to each application. This will also ease GDRP compliance because deleting a user in Keycloak will delete the user from
|
||||||
|
every app. The SCIM protocol is standard, comprehensible and easy to implement. It's a perfect fit for our goal.
|
||||||
|
|
||||||
### Technical choices
|
We chose to build application extensions/plugins because it's easier to deploy and thus will benefit to a larger portion
|
||||||
|
of the FOSS community.
|
||||||
The SCIM protocol is standard, comprehensible and easy to implement. It's a perfect fit for our goal.
|
|
||||||
|
|
||||||
We chose to build application extensions/plugins because it's easier to deploy and thus will benefit to a larger portion of the FOSS community.
|
|
||||||
|
|
||||||
#### Keycloak specific
|
#### Keycloak specific
|
||||||
|
|
||||||
This extension uses 3 concepts in KC :
|
This extension uses 3 concepts in KeyCloack :
|
||||||
- Event Listener : it's used to listens for changes and transform them in SCIM calls.
|
|
||||||
- Federation Provider : it's used to set up all the SCIM service providers without creating our own UI.
|
|
||||||
- JPA Entity Provider : it's used to save the mapping between the local IDs and the service providers IDs.
|
|
||||||
|
|
||||||
Because the event listener is the source of the SCIM flow, and it is not cancelable, we can't have strictly consistent behavior in case of SCIM service provider failure.
|
- Event Listener : used to listen for changes within Keycloack (e.g. User creation, Group deletion...) and propagate
|
||||||
|
them to registered SCIM service providers through SCIM requests.
|
||||||
|
- Federation Provider : used to set up all the SCIM service providers endpoint without creating our own UI.
|
||||||
|
- JPA Entity Provider : used to save the mapping between the local IDs and the service providers IDs.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Installation (quick)
|
### Development mode
|
||||||
|
|
||||||
1. Download the [latest version](https://lab.libreho.st/libre.sh/scim/keycloak-scim/-/jobs/artifacts/main/raw/build/libs/keycloak-scim-1.0-SNAPSHOT-all.jar?job=package)
|
From the repository root :
|
||||||
|
|
||||||
|
* Launch the docker-compose image (composed of a postgre and keycloack instance runing on localhost:8080) :
|
||||||
|
``docker compose up -d``
|
||||||
|
* Execute ``gradle jar shadowJar && docker compose restart keycloak`` to build extension and update the Keycloack
|
||||||
|
instance
|
||||||
|
* You can access extension logs through ``docker compose logs -f``
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
1. Download
|
||||||
|
the [latest version](https://lab.libreho.st/libre.sh/scim/keycloak-scim/-/jobs/artifacts/main/raw/build/libs/keycloak-scim-1.0-SNAPSHOT-all.jar?job=package)
|
||||||
2. Put it in `/opt/keycloak/providers/`.
|
2. Put it in `/opt/keycloak/providers/`.
|
||||||
|
|
||||||
It's also possible to build your own custom image if you run Keycloak in a [container](/docs/container.md).
|
It's also possible to build your own custom image if you run Keycloak in a [container](/docs/container.md).
|
||||||
|
@ -38,7 +62,7 @@ Other [installation options](/docs/installation.md) are available.
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
#### Add the event listerner
|
#### Enable SCIM Event listeners
|
||||||
|
|
||||||
1. Go to `Admin Console > Events > Config`.
|
1. Go to `Admin Console > Events > Config`.
|
||||||
2. Add `scim` in `Event Listeners`.
|
2. Add `scim` in `Event Listeners`.
|
||||||
|
@ -46,37 +70,35 @@ Other [installation options](/docs/installation.md) are available.
|
||||||
|
|
||||||
![Event listener page](/docs/img/event-listener-page.png)
|
![Event listener page](/docs/img/event-listener-page.png)
|
||||||
|
|
||||||
#### Create a federation provider
|
#### Register SCIM Service Providers
|
||||||
|
|
||||||
1. Go to `Admin Console > User Federation`.
|
1. Go to `Admin Console > Realm Settings > Events`.
|
||||||
2. Click on `Add provider`.
|
2. Add `scim` to the list of event listers
|
||||||
3. Select `scim`.
|
3. Save
|
||||||
4. Configure the provider ([see](#configuration)).
|
|
||||||
5. Save.
|
|
||||||
|
|
||||||
![Federation provider page](/docs/img/federation-provider-page.png)
|
![Federation provider page](/docs/img/federation-provider-page.png)
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
Add the endpoint - for a local set up you have to add the two containers in a docker network and use the container ip see [here](https://docs.docker.com/engine/reference/commandline/network/)
|
Add the endpoint - for a local set up you have to add the two containers in a docker network and use the container ip
|
||||||
If you use the [rocketchat app](https://lab.libreho.st/libre.sh/scim/rocketchat-scim) you get the endpoint from your rocket Chat Scim Adapter App Details.
|
see [here](https://docs.docker.com/engine/reference/commandline/network/)
|
||||||
|
If you use the [rocketchat app](https://lab.libreho.st/libre.sh/scim/rocketchat-scim) you get the endpoint from your
|
||||||
|
rocket Chat Scim Adapter App Details.
|
||||||
Endpoint content type is application/json.
|
Endpoint content type is application/json.
|
||||||
Auth mode Bearer or None for local test setup.
|
Auth mode Bearer or None for local test setup.
|
||||||
Copy the bearer token from your app details in rocketchat.
|
Copy the bearer token from your app details in rocketchat.
|
||||||
|
|
||||||
If you enable import during sync then you can choose between to following import actions:
|
If you enable import during sync then you can choose between to following import actions:
|
||||||
|
|
||||||
- Create Local - adds users to keycloak
|
- Create Local - adds users to keycloak
|
||||||
- Nothing
|
- Nothing
|
||||||
- Delete Remote - deletes users from the remote application
|
- Delete Remote - deletes users from the remote application
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Sync
|
### Sync
|
||||||
|
|
||||||
You can set up a periodic sync for all users or just changed users - it's not necesarry. You can either do:
|
You can set up a periodic sync for all users or just changed users - it's not mandatory. You can either do:
|
||||||
|
|
||||||
- Periodic Full Sync
|
- Periodic Full Sync
|
||||||
- Periodic Changed User Sync
|
- Periodic Changed User Sync
|
||||||
|
|
||||||
|
|
||||||
**[License AGPL](/LICENSE)**
|
**[License AGPL](/LICENSE)**
|
||||||
|
|
17
build.gradle
17
build.gradle
|
@ -1,7 +1,8 @@
|
||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||||
id "org.sonarqube" version "5.0.0.4638"
|
id "org.sonarqube" version "5.1.0.4882"
|
||||||
|
id "com.github.ben-manes.versions" version "0.51.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'sh.libre.scim'
|
group = 'sh.libre.scim'
|
||||||
|
@ -16,12 +17,12 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly 'org.keycloak:keycloak-core:23.0.4'
|
compileOnly 'org.keycloak:keycloak-core:26.0.1'
|
||||||
compileOnly 'org.keycloak:keycloak-server-spi:23.0.4'
|
compileOnly 'org.keycloak:keycloak-server-spi:26.0.1'
|
||||||
compileOnly 'org.keycloak:keycloak-server-spi-private:23.0.4'
|
compileOnly 'org.keycloak:keycloak-server-spi-private:26.0.1'
|
||||||
compileOnly 'org.keycloak:keycloak-services:23.0.4'
|
compileOnly 'org.keycloak:keycloak-services:26.0.1'
|
||||||
compileOnly 'org.keycloak:keycloak-model-jpa:23.0.4'
|
compileOnly 'org.keycloak:keycloak-model-jpa:26.0.1'
|
||||||
implementation 'io.github.resilience4j:resilience4j-retry:2.2.0'
|
implementation 'io.github.resilience4j:resilience4j-retry:2.2.0'
|
||||||
implementation 'de.captaingoldfish:scim-sdk-common:1.21.1'
|
implementation 'de.captaingoldfish:scim-sdk-common:1.26.0'
|
||||||
implementation 'de.captaingoldfish:scim-sdk-client:1.21.1'
|
implementation 'de.captaingoldfish:scim-sdk-client:1.26.0'
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
keycloak:
|
keycloak:
|
||||||
image: quay.io/keycloak/keycloak:23.0.3
|
image: quay.io/keycloak/keycloak:26.0.1
|
||||||
build: .
|
build: .
|
||||||
command: start-dev
|
command: start-dev
|
||||||
volumes:
|
volumes:
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 81 KiB |
Binary file not shown.
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 104 KiB |
|
@ -37,7 +37,7 @@ public class ScimExceptionHandler {
|
||||||
session.getTransactionManager().rollback();
|
session.getTransactionManager().rollback();
|
||||||
LOGGER.error("TRANSACTION ROLLBACK - " + errorMessage, e);
|
LOGGER.error("TRANSACTION ROLLBACK - " + errorMessage, e);
|
||||||
} else {
|
} else {
|
||||||
LOGGER.warn(errorMessage);
|
LOGGER.warn(errorMessage, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,13 +119,13 @@ public abstract class AbstractScimService<K extends RoleMapperModel, S extends R
|
||||||
syncRes.increaseUpdated();
|
syncRes.increaseUpdated();
|
||||||
} catch (InvalidResponseFromScimEndpointException e) {
|
} catch (InvalidResponseFromScimEndpointException e) {
|
||||||
if (skipOrStopStrategy.allowPartialSynchronizationWhenPushingToScim(this.getConfiguration())) {
|
if (skipOrStopStrategy.allowPartialSynchronizationWhenPushingToScim(this.getConfiguration())) {
|
||||||
LOGGER.warn("Error while syncing " + id + " to endpoint " + getConfiguration().getEndPoint());
|
LOGGER.warn("Error while syncing " + id + " to endpoint " + getConfiguration().getEndPoint(), e);
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
} catch (InconsistentScimMappingException e) {
|
} catch (InconsistentScimMappingException e) {
|
||||||
if (skipOrStopStrategy.allowPartialSynchronizationWhenPushingToScim(this.getConfiguration())) {
|
if (skipOrStopStrategy.allowPartialSynchronizationWhenPushingToScim(this.getConfiguration())) {
|
||||||
LOGGER.warn("Inconsistent data for element " + id + " and endpoint " + getConfiguration().getEndPoint());
|
LOGGER.warn("Inconsistent data for element " + id + " and endpoint " + getConfiguration().getEndPoint(), e);
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
@ -155,20 +155,20 @@ public abstract class AbstractScimService<K extends RoleMapperModel, S extends R
|
||||||
}
|
}
|
||||||
} catch (UnexpectedScimDataException e) {
|
} catch (UnexpectedScimDataException e) {
|
||||||
if (skipOrStopStrategy.skipInvalidDataFromScimEndpoint(getConfiguration())) {
|
if (skipOrStopStrategy.skipInvalidDataFromScimEndpoint(getConfiguration())) {
|
||||||
LOGGER.warn("[SCIM] Skipping element synchronisation because of invalid Scim Data for element " + resource.getId() + " : " + e.getMessage());
|
LOGGER.warn("[SCIM] Skipping element synchronisation because of invalid Scim Data for element " + resource.getId() + " : " + e.getMessage(), e);
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
} catch (InconsistentScimMappingException e) {
|
} catch (InconsistentScimMappingException e) {
|
||||||
if (skipOrStopStrategy.allowPartialSynchronizationWhenPullingFromScim(getConfiguration())) {
|
if (skipOrStopStrategy.allowPartialSynchronizationWhenPullingFromScim(getConfiguration())) {
|
||||||
LOGGER.warn("[SCIM] Skipping element synchronisation because of inconsistent mapping for element " + resource.getId() + " : " + e.getMessage());
|
LOGGER.warn("[SCIM] Skipping element synchronisation because of inconsistent mapping for element " + resource.getId() + " : " + e.getMessage(), e);
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
} catch (InvalidResponseFromScimEndpointException e) {
|
} catch (InvalidResponseFromScimEndpointException e) {
|
||||||
// Can only occur in case of a DELETE_REMOTE conflict action
|
// Can only occur in case of a DELETE_REMOTE conflict action
|
||||||
if (skipOrStopStrategy.allowPartialSynchronizationWhenPullingFromScim(getConfiguration())) {
|
if (skipOrStopStrategy.allowPartialSynchronizationWhenPullingFromScim(getConfiguration())) {
|
||||||
LOGGER.warn("[SCIM] Could not delete SCIM resource " + resource.getId() + " during synchronisation");
|
LOGGER.warn("[SCIM] Could not delete SCIM resource " + resource.getId() + " during synchronisation", e);
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue