diff --git a/src/main/java/sh/libre/scim/core/GroupScimClient.java b/src/main/java/sh/libre/scim/core/GroupScimClient.java index 1483d3bda3..b4c911d864 100644 --- a/src/main/java/sh/libre/scim/core/GroupScimClient.java +++ b/src/main/java/sh/libre/scim/core/GroupScimClient.java @@ -30,6 +30,11 @@ public class GroupScimClient implements ScimClientInterface { throw new UnsupportedOperationException(); } + @Override + public ScrimProviderConfiguration getConfiguration() { + throw new UnsupportedOperationException(); + } + @Override public void close() throws Exception { throw new UnsupportedOperationException(); diff --git a/src/main/java/sh/libre/scim/core/ScimClientInterface.java b/src/main/java/sh/libre/scim/core/ScimClientInterface.java index d28f289fae..5316bf58a5 100644 --- a/src/main/java/sh/libre/scim/core/ScimClientInterface.java +++ b/src/main/java/sh/libre/scim/core/ScimClientInterface.java @@ -39,4 +39,9 @@ public interface ScimClientInterface extends AutoClos * @param result the synchronization result to update for indicating triggered operations (e.g. user deletions) */ void sync(SynchronizationResult result); + + /** + * @return the {@link ScrimProviderConfiguration} corresponding to this client. + */ + ScrimProviderConfiguration getConfiguration(); } diff --git a/src/main/java/sh/libre/scim/core/ScimDispatcher.java b/src/main/java/sh/libre/scim/core/ScimDispatcher.java index 3434911014..9501586d3e 100644 --- a/src/main/java/sh/libre/scim/core/ScimDispatcher.java +++ b/src/main/java/sh/libre/scim/core/ScimDispatcher.java @@ -5,66 +5,97 @@ import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import sh.libre.scim.storage.ScimStorageProviderFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import java.util.function.Consumer; -import java.util.stream.Stream; /** * In charge of sending SCIM Request to all registered Scim endpoints. */ public class ScimDispatcher { - private static final Logger LOGGER = Logger.getLogger(ScimDispatcher.class); + private static final Logger logger = Logger.getLogger(ScimDispatcher.class); private final KeycloakSession session; + private final List userScimClients = new ArrayList<>(); + private final List groupScimClients = new ArrayList<>(); public ScimDispatcher(KeycloakSession session) { this.session = session; - } - - public void dispatchUserModificationToAll(Consumer operationToDispatch) { - getAllSCIMServer(Scope.USER).forEach(scimServerConfiguration -> dispatchUserModificationToOne(scimServerConfiguration, operationToDispatch)); - } - - public void dispatchUserModificationToOne(ComponentModel scimServerConfiguration, Consumer operationToDispatch) { - LOGGER.infof("%s %s %s %s", scimServerConfiguration.getId(), scimServerConfiguration.getName(), scimServerConfiguration.getProviderId(), scimServerConfiguration.getProviderType()); - try (UserScimClient client = UserScimClient.newUserScimClient(scimServerConfiguration, session)) { - operationToDispatch.accept(client); - } catch (Exception e) { - LOGGER.error(e); - } - } - - public void dispatchGroupModificationToAll(Consumer operationToDispatch) { - getAllSCIMServer(Scope.GROUP).forEach(scimServerConfiguration -> dispatchGroupModificationToOne(scimServerConfiguration, operationToDispatch)); - } - - public void dispatchGroupModificationToOne(ComponentModel scimServerConfiguration, Consumer operationToDispatch) { - LOGGER.infof("%s %s %s %s", scimServerConfiguration.getId(), scimServerConfiguration.getName(), scimServerConfiguration.getProviderId(), scimServerConfiguration.getProviderType()); - try (GroupScimClient client = GroupScimClient.newGroupScimClient(scimServerConfiguration, session)) { - operationToDispatch.accept(client); - } catch (Exception e) { - LOGGER.error(e); - } + refreshActiveScimEndpoints(); } /** - * @param scope The {@link Scope} to consider (User or Group) - * @return all enabled registered Scim endpoints with propagation enabled for the given scope + * Lists all active ScimStorageProviderFactory and create new ScimClients for each of them */ - private Stream getAllSCIMServer(Scope scope) { - // TODO : we could initiative this list once and invalidate it when configuration changes + public void refreshActiveScimEndpoints() { + try { + // Step 1: close existing clients + for (GroupScimClient c : groupScimClients) { + c.close(); + } + for (UserScimClient c : userScimClients) { + c.close(); + } - String propagationConfKey = switch (scope) { - case GROUP -> ScrimProviderConfiguration.CONF_KEY_PROPAGATION_GROUP; - case USER -> ScrimProviderConfiguration.CONF_KEY_PROPAGATION_USER; - }; - return session.getContext().getRealm().getComponentsStream() - .filter(m -> ScimStorageProviderFactory.ID.equals(m.getProviderId()) - && m.get("enabled", true) - && m.get(propagationConfKey, false)); + // Step 2: Get All SCIM endpoints defined in Admin Console (enabled ScimStorageProviderFactory) + session.getContext().getRealm().getComponentsStream() + .filter(m -> ScimStorageProviderFactory.ID.equals(m.getProviderId()) + && m.get("enabled", true)) + .forEach(scimEndpoint -> { + // Step 3 : create scim clients for each endpoint + if (scimEndpoint.get(ScrimProviderConfiguration.CONF_KEY_PROPAGATION_GROUP, false)) { + GroupScimClient groupScimClient = GroupScimClient.newGroupScimClient(scimEndpoint, session); + groupScimClients.add(groupScimClient); + } + if (scimEndpoint.get(ScrimProviderConfiguration.CONF_KEY_PROPAGATION_USER, false)) { + UserScimClient userScimClient = UserScimClient.newUserScimClient(scimEndpoint, session); + userScimClients.add(userScimClient); + } + }); + } catch (Exception e) { + logger.error("[SCIM] Error while refreshing scim clients ", e); + // TODO : how to handle exception here ? + } } - public enum Scope { - USER, GROUP + public void dispatchUserModificationToAll(Consumer operationToDispatch) { + // TODO should not be required to launch a refresh here : we should refresh clients only if an endpoint configuration changes + refreshActiveScimEndpoints(); + userScimClients.forEach(operationToDispatch); + } + + public void dispatchGroupModificationToAll(Consumer operationToDispatch) { + // TODO should not be required to launch a refresh here : we should refresh clients only if an endpoint configuration changes + refreshActiveScimEndpoints(); + groupScimClients.forEach(operationToDispatch); + } + + public void dispatchUserModificationToOne(ComponentModel scimServerConfiguration, Consumer operationToDispatch) { + // TODO should not be required to launch a refresh here : we should refresh clients only if an endpoint configuration changes + refreshActiveScimEndpoints(); + + // Scim client should already have been created + Optional matchingClient = userScimClients.stream().filter(u -> u.getConfiguration().getId().equals(scimServerConfiguration.getId())).findFirst(); + if (matchingClient.isPresent()) { + operationToDispatch.accept(matchingClient.get()); + } else { + logger.error("[SCIM] Could not find a Scim Client matching endpoint configuration" + scimServerConfiguration.getId()); + } + } + + + public void dispatchGroupModificationToOne(ComponentModel scimServerConfiguration, Consumer operationToDispatch) { + // TODO should not be required to launch a refresh here : we should refresh clients only if an endpoint configuration changes + refreshActiveScimEndpoints(); + + // Scim client should already have been created + Optional matchingClient = groupScimClients.stream().filter(u -> u.getConfiguration().getId().equals(scimServerConfiguration.getId())).findFirst(); + if (matchingClient.isPresent()) { + operationToDispatch.accept(matchingClient.get()); + } else { + logger.error("[SCIM] Could not find a Scim Client matching endpoint configuration" + scimServerConfiguration.getId()); + } } } diff --git a/src/main/java/sh/libre/scim/core/UserScimClient.java b/src/main/java/sh/libre/scim/core/UserScimClient.java index 4f257a0fca..8d2f25279b 100644 --- a/src/main/java/sh/libre/scim/core/UserScimClient.java +++ b/src/main/java/sh/libre/scim/core/UserScimClient.java @@ -275,6 +275,11 @@ public class UserScimClient implements ScimClientInterface { } } + @Override + public ScrimProviderConfiguration getConfiguration() { + return this.scimProviderConfiguration; + } + @Override public void close() {