Exception Handler - Step 1: basic wiring

This commit is contained in:
Alex Morel 2024-06-25 09:14:13 +02:00
parent 51d4837c19
commit 72e597a09c
5 changed files with 53 additions and 19 deletions

View file

@ -132,7 +132,7 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
protected abstract Stream<RMM> getResourceStream(); protected abstract Stream<RMM> getResourceStream();
public void importResources(SynchronizationResult syncRes) { public void importResources(SynchronizationResult syncRes) throws ScimPropagationException {
LOGGER.info("Import"); LOGGER.info("Import");
try { try {
for (S resource : scimClient.listResources()) { for (S resource : scimClient.listResources()) {
@ -180,13 +180,17 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
} }
} }
} catch (Exception e) { } catch (Exception e) {
// TODO should we stop and throw ScimPropagationException here ?
LOGGER.error(e); LOGGER.error(e);
e.printStackTrace(); e.printStackTrace();
syncRes.increaseFailed(); syncRes.increaseFailed();
} }
} }
} catch (ResponseException e) { } catch (ResponseException e) {
throw new RuntimeException(e); // TODO should we stop and throw ScimPropagationException here ?
LOGGER.error(e);
e.printStackTrace();
syncRes.increaseFailed();
} }
} }
@ -216,7 +220,7 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
try { try {
return new URI("%s/%s".formatted(type.getEndpoint(), externalId.asString())); return new URI("%s/%s".formatted(type.getEndpoint(), externalId.asString()));
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
throw new IllegalStateException("should never occur: can not format URI for type %s and id %s".formatted(type, externalId) , e); throw new IllegalStateException("should never occur: can not format URI for type %s and id %s".formatted(type, externalId), e);
} }
} }

View file

@ -17,6 +17,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
public class ScimClient<S extends ResourceNode> implements AutoCloseable { public class ScimClient<S extends ResourceNode> implements AutoCloseable {
private static final Logger LOGGER = Logger.getLogger(ScimClient.class); private static final Logger LOGGER = Logger.getLogger(ScimClient.class);
@ -62,10 +63,11 @@ public class ScimClient<S extends ResourceNode> implements AutoCloseable {
return new ScimClient(scimRequestBuilder, scimResourceType); return new ScimClient(scimRequestBuilder, scimResourceType);
} }
public EntityOnRemoteScimId create(KeycloakId id, S scimForCreation) { public EntityOnRemoteScimId create(KeycloakId id, S scimForCreation) throws ScimPropagationException {
if (scimForCreation.getId().isPresent()) { Optional<String> scimForCreationId = scimForCreation.getId();
throw new IllegalArgumentException( if (scimForCreationId.isPresent()) {
"%s is already created on remote with id %s".formatted(id, scimForCreation.getId().get()) throw new ScimPropagationException(
"%s is already created on remote with id %s".formatted(id, scimForCreationId.get())
); );
} }
Retry retry = retryRegistry.retry("create-%s".formatted(id.asString())); Retry retry = retryRegistry.retry("create-%s".formatted(id.asString()));

View file

@ -19,6 +19,7 @@ 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 ScimExceptionHandler exceptionHandler;
private boolean clientsInitialized = false; private boolean clientsInitialized = false;
private final List<UserScimService> userScimServices = new ArrayList<>(); private final List<UserScimService> userScimServices = new ArrayList<>();
private final List<GroupScimService> groupScimServices = new ArrayList<>(); private final List<GroupScimService> groupScimServices = new ArrayList<>();
@ -26,6 +27,7 @@ public class ScimDispatcher {
public ScimDispatcher(KeycloakSession session) { public ScimDispatcher(KeycloakSession session) {
this.session = session; this.session = session;
this.exceptionHandler = new ScimExceptionHandler(session);
} }
/** /**
@ -78,7 +80,7 @@ public class ScimDispatcher {
operationToDispatch.acceptThrows(userScimService); operationToDispatch.acceptThrows(userScimService);
servicesCorrectlyPropagated.add(userScimService); servicesCorrectlyPropagated.add(userScimService);
} catch (ScimPropagationException e) { } catch (ScimPropagationException e) {
logAndRollback(userScimService.getConfiguration(), e); exceptionHandler.handleException(userScimService.getConfiguration(), e);
} }
}); });
// TODO we could iterate on servicesCorrectlyPropagated to undo modification // TODO we could iterate on servicesCorrectlyPropagated to undo modification
@ -93,7 +95,7 @@ public class ScimDispatcher {
operationToDispatch.acceptThrows(groupScimService); operationToDispatch.acceptThrows(groupScimService);
servicesCorrectlyPropagated.add(groupScimService); servicesCorrectlyPropagated.add(groupScimService);
} catch (ScimPropagationException e) { } catch (ScimPropagationException e) {
logAndRollback(groupScimService.getConfiguration(), e); exceptionHandler.handleException(groupScimService.getConfiguration(), e);
} }
}); });
// TODO we could iterate on servicesCorrectlyPropagated to undo modification // TODO we could iterate on servicesCorrectlyPropagated to undo modification
@ -109,10 +111,10 @@ public class ScimDispatcher {
operationToDispatch.acceptThrows(matchingClient.get()); operationToDispatch.acceptThrows(matchingClient.get());
logger.infof("[SCIM] User operation dispatched to SCIM server %s", matchingClient.get().getConfiguration().getId()); logger.infof("[SCIM] User operation dispatched to SCIM server %s", matchingClient.get().getConfiguration().getId());
} catch (ScimPropagationException e) { } catch (ScimPropagationException e) {
logAndRollback(matchingClient.get().getConfiguration(), e); exceptionHandler.handleException(matchingClient.get().getConfiguration(), e);
} }
} else { } else {
logger.error("[SCIM] Could not find a Scim Client matching endpoint configuration" + scimServerConfiguration.getId()); logger.error("[SCIM] Could not find a Scim Client matching User endpoint configuration" + scimServerConfiguration.getId());
} }
} }
@ -126,10 +128,10 @@ public class ScimDispatcher {
operationToDispatch.acceptThrows(matchingClient.get()); operationToDispatch.acceptThrows(matchingClient.get());
logger.infof("[SCIM] Group operation dispatched to SCIM server %s", matchingClient.get().getConfiguration().getId()); logger.infof("[SCIM] Group operation dispatched to SCIM server %s", matchingClient.get().getConfiguration().getId());
} catch (ScimPropagationException e) { } catch (ScimPropagationException e) {
logAndRollback(matchingClient.get().getConfiguration(), e); exceptionHandler.handleException(matchingClient.get().getConfiguration(), e);
} }
} else { } else {
logger.error("[SCIM] Could not find a Scim Client matching endpoint configuration" + scimServerConfiguration.getId()); logger.error("[SCIM] Could not find a Scim Client matching Group endpoint configuration" + scimServerConfiguration.getId());
} }
} }
@ -144,11 +146,6 @@ public class ScimDispatcher {
userScimServices.clear(); userScimServices.clear();
} }
private void logAndRollback(ScrimProviderConfiguration scimServerConfiguration, ScimPropagationException e) {
logger.error("[SCIM] Error while propagating to SCIM endpoint " + scimServerConfiguration.getId(), e);
// TODO session.getTransactionManager().rollback();
}
private void initializeClientsIfNeeded() { private void initializeClientsIfNeeded() {
if (!clientsInitialized) { if (!clientsInitialized) {
clientsInitialized = true; clientsInitialized = true;

View file

@ -0,0 +1,31 @@
package sh.libre.scim.core;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
/**
* In charge of dealing with SCIM exceptions by ignoring, logging or rollback transaction according to :
* - The context in which it occurs (sync, user creation...)
* - The related SCIM endpoint and its configuration
* - The thrown exception itself
*/
public class ScimExceptionHandler {
private static final Logger logger = Logger.getLogger(ScimDispatcher.class);
private final KeycloakSession session;
public ScimExceptionHandler(KeycloakSession session) {
this.session = session;
}
/**
* Handles the given exception by loggin and/or rollback transaction.
*
* @param scimProviderConfiguration the configuration of the endpoint for which the propagation exception occured
* @param e the occuring exception
*/
public void handleException(ScrimProviderConfiguration scimProviderConfiguration, ScimPropagationException e) {
logger.error("[SCIM] Error while propagating to SCIM endpoint " + scimProviderConfiguration.getId(), e);
// TODO session.getTransactionManager().rollback();
}
}

View file

@ -107,7 +107,7 @@ public class ScimEventListenerProvider implements EventListenerProvider {
ScimResourceType type = switch (rawResourceType) { ScimResourceType type = switch (rawResourceType) {
case "users" -> ScimResourceType.USER; case "users" -> ScimResourceType.USER;
case "groups" -> ScimResourceType.GROUP; case "groups" -> ScimResourceType.GROUP;
default -> throw new IllegalArgumentException("Unsuported resource type: " + rawResourceType); default -> throw new IllegalArgumentException("Unsupported resource type: " + rawResourceType);
}; };
KeycloakId id = new KeycloakId(matcher.group(2)); KeycloakId id = new KeycloakId(matcher.group(2));
handleRoleMappingEvent(event, type, id); handleRoleMappingEvent(event, type, id);