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();
}
@Override
public ScrimProviderConfiguration getConfiguration() {
throw new UnsupportedOperationException();
}
@Override
public void close() throws Exception {
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)
*/
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 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<UserScimClient> userScimClients = new ArrayList<>();
private final List<GroupScimClient> groupScimClients = new ArrayList<>();
public ScimDispatcher(KeycloakSession session) {
this.session = session;
}
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);
}
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<ComponentModel> 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<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
public void close() {