Avoid recreating ScimClients at every change propagation : code refactoring

This commit is contained in:
Alex Morel 2024-06-18 17:36:15 +02:00
parent f00130d37a
commit 633291d401
4 changed files with 88 additions and 42 deletions

View file

@ -30,6 +30,11 @@ public class GroupScimClient implements ScimClientInterface<GroupModel> {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public ScrimProviderConfiguration getConfiguration() {
throw new UnsupportedOperationException();
}
@Override @Override
public void close() throws Exception { public void close() throws Exception {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();

View file

@ -39,4 +39,9 @@ public interface ScimClientInterface<M extends RoleMapperModel> extends AutoClos
* @param result the synchronization result to update for indicating triggered operations (e.g. user deletions) * @param result the synchronization result to update for indicating triggered operations (e.g. user deletions)
*/ */
void sync(SynchronizationResult result); void sync(SynchronizationResult result);
/**
* @return the {@link ScrimProviderConfiguration} corresponding to this client.
*/
ScrimProviderConfiguration getConfiguration();
} }

View file

@ -5,66 +5,97 @@ import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import sh.libre.scim.storage.ScimStorageProviderFactory; 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.function.Consumer;
import java.util.stream.Stream;
/** /**
* In charge of sending SCIM Request to all registered Scim endpoints. * In charge of sending SCIM Request to all registered Scim endpoints.
*/ */
public class ScimDispatcher { 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 KeycloakSession session;
private final List<UserScimClient> userScimClients = new ArrayList<>();
private final List<GroupScimClient> groupScimClients = new ArrayList<>();
public ScimDispatcher(KeycloakSession session) { public ScimDispatcher(KeycloakSession session) {
this.session = session; this.session = session;
} refreshActiveScimEndpoints();
public void dispatchUserModificationToAll(Consumer<UserScimClient> operationToDispatch) {
getAllSCIMServer(Scope.USER).forEach(scimServerConfiguration -> dispatchUserModificationToOne(scimServerConfiguration, operationToDispatch));
}
public void dispatchUserModificationToOne(ComponentModel scimServerConfiguration, Consumer<UserScimClient> 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<GroupScimClient> operationToDispatch) {
getAllSCIMServer(Scope.GROUP).forEach(scimServerConfiguration -> dispatchGroupModificationToOne(scimServerConfiguration, operationToDispatch));
}
public void dispatchGroupModificationToOne(ComponentModel scimServerConfiguration, Consumer<GroupScimClient> 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);
}
} }
/** /**
* @param scope The {@link Scope} to consider (User or Group) * Lists all active ScimStorageProviderFactory and create new ScimClients for each of them
* @return all enabled registered Scim endpoints with propagation enabled for the given scope
*/ */
private Stream<ComponentModel> getAllSCIMServer(Scope scope) { public void refreshActiveScimEndpoints() {
// TODO : we could initiative this list once and invalidate it when configuration changes try {
// Step 1: close existing clients
String propagationConfKey = switch (scope) { for (GroupScimClient c : groupScimClients) {
case GROUP -> ScrimProviderConfiguration.CONF_KEY_PROPAGATION_GROUP; c.close();
case USER -> ScrimProviderConfiguration.CONF_KEY_PROPAGATION_USER; }
}; for (UserScimClient c : userScimClients) {
return session.getContext().getRealm().getComponentsStream() c.close();
.filter(m -> ScimStorageProviderFactory.ID.equals(m.getProviderId())
&& m.get("enabled", true)
&& m.get(propagationConfKey, false));
} }
public enum Scope { // Step 2: Get All SCIM endpoints defined in Admin Console (enabled ScimStorageProviderFactory)
USER, GROUP 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 void dispatchUserModificationToAll(Consumer<UserScimClient> 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<GroupScimClient> 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<UserScimClient> 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<UserScimClient> 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<GroupScimClient> 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<GroupScimClient> 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());
}
} }
} }

View file

@ -275,6 +275,11 @@ public class UserScimClient implements ScimClientInterface<UserModel> {
} }
} }
@Override
public ScrimProviderConfiguration getConfiguration() {
return this.scimProviderConfiguration;
}
@Override @Override
public void close() { public void close() {