diff --git a/src/main/java/sh/libre/scim/core/AbstractScimService.java b/src/main/java/sh/libre/scim/core/AbstractScimService.java index 4e42351c24..8f92a22681 100644 --- a/src/main/java/sh/libre/scim/core/AbstractScimService.java +++ b/src/main/java/sh/libre/scim/core/AbstractScimService.java @@ -35,7 +35,7 @@ public abstract class AbstractScimService { KeycloakId id = getId(resource); LOGGER.infof("Reconciling local resource %s", id); if (!isSkipRefresh(resource)) { try { - findById(id).get(); - LOGGER.info("Replacing it"); - replace(resource); - } catch (NoSuchElementException e) { - LOGGER.info("Creating it"); - create(resource); + try { + findById(id).get(); + LOGGER.info("Replacing it"); + replace(resource); + } catch (NoSuchElementException e) { + LOGGER.info("Creating it"); + create(resource); + } + syncRes.increaseUpdated(); + } catch (ScimPropagationException e) { + // TODO handle exception } - syncRes.increaseUpdated(); } }); } @@ -187,7 +192,7 @@ public abstract class AbstractScimService implements AutoCloseable { - private final Logger logger = Logger.getLogger(ScimClient.class); + private static final Logger LOGGER = Logger.getLogger(ScimClient.class); private final RetryRegistry retryRegistry; @@ -83,8 +83,8 @@ public class ScimClient implements AutoCloseable { private void checkResponseIsSuccess(ServerResponse response) { if (!response.isSuccess()) { - logger.warn(response.getResponseBody()); - logger.warn(response.getHttpStatus()); + LOGGER.warn(response.getResponseBody()); + LOGGER.warn(response.getHttpStatus()); } } @@ -98,7 +98,7 @@ public class ScimClient implements AutoCloseable { public void replace(EntityOnRemoteScimId externalId, S scimForReplace) { Retry retry = retryRegistry.retry("replace-%s".formatted(externalId.asString())); - logger.warn(scimForReplace); + LOGGER.warn(scimForReplace); ServerResponse response = retry.executeSupplier(() -> scimRequestBuilder .update(getResourceClass(), getScimEndpoint(), externalId.asString()) .setResource(scimForReplace) diff --git a/src/main/java/sh/libre/scim/core/ScimDispatcher.java b/src/main/java/sh/libre/scim/core/ScimDispatcher.java index 6804c06554..6be68dc3d2 100644 --- a/src/main/java/sh/libre/scim/core/ScimDispatcher.java +++ b/src/main/java/sh/libre/scim/core/ScimDispatcher.java @@ -6,9 +6,10 @@ import org.keycloak.models.KeycloakSession; import sh.libre.scim.storage.ScimEndpointConfigurationStorageProviderFactory; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; -import java.util.function.Consumer; +import java.util.Set; /** * In charge of sending SCIM Request to all registered Scim endpoints. @@ -69,38 +70,64 @@ public class ScimDispatcher { } } - public void dispatchUserModificationToAll(Consumer operationToDispatch) { + public void dispatchUserModificationToAll(SCIMPropagationConsumer operationToDispatch) { initializeClientsIfNeeded(); - userScimServices.forEach(operationToDispatch); - logger.infof("[SCIM] User operation dispatched to %d SCIM server", userScimServices.size()); + Set servicesCorrectlyPropagated = new LinkedHashSet<>(); + userScimServices.forEach(userScimService -> { + try { + operationToDispatch.acceptThrows(userScimService); + servicesCorrectlyPropagated.add(userScimService); + } catch (ScimPropagationException e) { + logAndRollback(userScimService.getConfiguration(), e); + } + }); + // TODO we could iterate on servicesCorrectlyPropagated to undo modification + logger.infof("[SCIM] User operation dispatched to %d SCIM server", servicesCorrectlyPropagated.size()); } - public void dispatchGroupModificationToAll(Consumer operationToDispatch) { + public void dispatchGroupModificationToAll(SCIMPropagationConsumer operationToDispatch) { initializeClientsIfNeeded(); - groupScimServices.forEach(operationToDispatch); - logger.infof("[SCIM] Group operation dispatched to %d SCIM server", groupScimServices.size()); + Set servicesCorrectlyPropagated = new LinkedHashSet<>(); + groupScimServices.forEach(groupScimService -> { + try { + operationToDispatch.acceptThrows(groupScimService); + servicesCorrectlyPropagated.add(groupScimService); + } catch (ScimPropagationException e) { + logAndRollback(groupScimService.getConfiguration(), e); + } + }); + // TODO we could iterate on servicesCorrectlyPropagated to undo modification + logger.infof("[SCIM] Group operation dispatched to %d SCIM server", servicesCorrectlyPropagated.size()); } - public void dispatchUserModificationToOne(ComponentModel scimServerConfiguration, Consumer operationToDispatch) { + public void dispatchUserModificationToOne(ComponentModel scimServerConfiguration, SCIMPropagationConsumer operationToDispatch) { initializeClientsIfNeeded(); // Scim client should already have been created Optional matchingClient = userScimServices.stream().filter(u -> u.getConfiguration().getId().equals(scimServerConfiguration.getId())).findFirst(); if (matchingClient.isPresent()) { - operationToDispatch.accept(matchingClient.get()); - logger.infof("[SCIM] User operation dispatched to SCIM server %s", matchingClient.get().getConfiguration().getId()); + try { + operationToDispatch.acceptThrows(matchingClient.get()); + logger.infof("[SCIM] User operation dispatched to SCIM server %s", matchingClient.get().getConfiguration().getId()); + } catch (ScimPropagationException e) { + logAndRollback(matchingClient.get().getConfiguration(), e); + } } else { logger.error("[SCIM] Could not find a Scim Client matching endpoint configuration" + scimServerConfiguration.getId()); } } - public void dispatchGroupModificationToOne(ComponentModel scimServerConfiguration, Consumer operationToDispatch) { + public void dispatchGroupModificationToOne(ComponentModel scimServerConfiguration, SCIMPropagationConsumer operationToDispatch) { initializeClientsIfNeeded(); // Scim client should already have been created Optional matchingClient = groupScimServices.stream().filter(u -> u.getConfiguration().getId().equals(scimServerConfiguration.getId())).findFirst(); if (matchingClient.isPresent()) { - operationToDispatch.accept(matchingClient.get()); - logger.infof("[SCIM] Group operation dispatched to SCIM server %s", matchingClient.get().getConfiguration().getId()); + try { + operationToDispatch.acceptThrows(matchingClient.get()); + logger.infof("[SCIM] Group operation dispatched to SCIM server %s", matchingClient.get().getConfiguration().getId()); + } catch (ScimPropagationException e) { + logAndRollback(matchingClient.get().getConfiguration(), e); + } } else { logger.error("[SCIM] Could not find a Scim Client matching endpoint configuration" + scimServerConfiguration.getId()); } @@ -117,10 +144,27 @@ public class ScimDispatcher { userScimServices.clear(); } + private void logAndRollback(ScrimProviderConfiguration scimServerConfiguration, ScimPropagationException e) { + logger.error("[SCIM] Error while propagating to SCIM endpoint " + scimServerConfiguration.getId(), e); + session.getTransactionManager().rollback(); + } + private void initializeClientsIfNeeded() { if (!clientsInitialized) { clientsInitialized = true; refreshActiveScimEndpoints(); } } + + /** + * A Consumer that throws ScimPropagationException. + * + * @param An {@link AbstractScimService to call} + */ + @FunctionalInterface + public interface SCIMPropagationConsumer { + + void acceptThrows(T elem) throws ScimPropagationException; + + } } diff --git a/src/main/java/sh/libre/scim/core/ScimPropagationException.java b/src/main/java/sh/libre/scim/core/ScimPropagationException.java index 145c7c4c0a..135b555961 100644 --- a/src/main/java/sh/libre/scim/core/ScimPropagationException.java +++ b/src/main/java/sh/libre/scim/core/ScimPropagationException.java @@ -1,4 +1,12 @@ package sh.libre.scim.core; -public class ScimPropagationException { +public class ScimPropagationException extends Exception { + + public ScimPropagationException(String message) { + super(message); + } + + public ScimPropagationException(String message, Exception e) { + super(message, e); + } }