Rollback on ScimPropagationException
This commit is contained in:
parent
81d9f5424c
commit
0cac71c4a1
4 changed files with 88 additions and 31 deletions
|
@ -35,7 +35,7 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
|
|||
this.scimClient = ScimClient.open(scimProviderConfiguration, type);
|
||||
}
|
||||
|
||||
public void create(RMM roleMapperModel) {
|
||||
public void create(RMM roleMapperModel) throws ScimPropagationException {
|
||||
boolean skip = isSkip(roleMapperModel);
|
||||
if (skip)
|
||||
return;
|
||||
|
@ -71,7 +71,7 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
|
|||
return keycloakSession;
|
||||
}
|
||||
|
||||
public void replace(RMM roleMapperModel) {
|
||||
public void replace(RMM roleMapperModel) throws ScimPropagationException {
|
||||
try {
|
||||
if (isSkip(roleMapperModel))
|
||||
return;
|
||||
|
@ -84,37 +84,42 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
|
|||
LOGGER.warnf("failed to replace resource %s, scim mapping not found", getId(roleMapperModel));
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(e);
|
||||
throw new ScimPropagationException("[SCIM] Error while replacing SCIM resource", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract S toScimForReplace(RMM roleMapperModel, EntityOnRemoteScimId externalId);
|
||||
|
||||
public void delete(KeycloakId id) {
|
||||
public void delete(KeycloakId id) throws ScimPropagationException {
|
||||
try {
|
||||
ScimResource resource = findById(id).get();
|
||||
EntityOnRemoteScimId externalId = resource.getExternalIdAsEntityOnRemoteScimId();
|
||||
scimClient.delete(externalId);
|
||||
getScimResourceDao().delete(resource);
|
||||
} catch (NoSuchElementException e) {
|
||||
LOGGER.warnf("Failed to delete resource %s, scim mapping not found", id);
|
||||
throw new ScimPropagationException("Failed to delete resource %s, scim mapping not found : " + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshResources(SynchronizationResult syncRes) {
|
||||
public void refreshResources(SynchronizationResult syncRes) throws ScimPropagationException {
|
||||
LOGGER.info("Refresh resources");
|
||||
getResourceStream().forEach(resource -> {
|
||||
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<RMM extends RoleMapperModel, S extends
|
|||
|
||||
protected abstract boolean entityExists(KeycloakId keycloakId);
|
||||
|
||||
public void sync(SynchronizationResult syncRes) {
|
||||
public void sync(SynchronizationResult syncRes) throws ScimPropagationException {
|
||||
if (this.scimProviderConfiguration.isSyncImport()) {
|
||||
this.importResources(syncRes);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
public class ScimClient<S extends ResourceNode> 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<S extends ResourceNode> implements AutoCloseable {
|
|||
|
||||
private void checkResponseIsSuccess(ServerResponse<S> 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<S extends ResourceNode> 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<S> response = retry.executeSupplier(() -> scimRequestBuilder
|
||||
.update(getResourceClass(), getScimEndpoint(), externalId.asString())
|
||||
.setResource(scimForReplace)
|
||||
|
|
|
@ -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<UserScimService> operationToDispatch) {
|
||||
public void dispatchUserModificationToAll(SCIMPropagationConsumer<UserScimService> operationToDispatch) {
|
||||
initializeClientsIfNeeded();
|
||||
userScimServices.forEach(operationToDispatch);
|
||||
logger.infof("[SCIM] User operation dispatched to %d SCIM server", userScimServices.size());
|
||||
Set<UserScimService> 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<GroupScimService> operationToDispatch) {
|
||||
public void dispatchGroupModificationToAll(SCIMPropagationConsumer<GroupScimService> operationToDispatch) {
|
||||
initializeClientsIfNeeded();
|
||||
groupScimServices.forEach(operationToDispatch);
|
||||
logger.infof("[SCIM] Group operation dispatched to %d SCIM server", groupScimServices.size());
|
||||
Set<GroupScimService> 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<UserScimService> operationToDispatch) {
|
||||
public void dispatchUserModificationToOne(ComponentModel scimServerConfiguration, SCIMPropagationConsumer<UserScimService> operationToDispatch) {
|
||||
initializeClientsIfNeeded();
|
||||
// Scim client should already have been created
|
||||
Optional<UserScimService> 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<GroupScimService> operationToDispatch) {
|
||||
public void dispatchGroupModificationToOne(ComponentModel scimServerConfiguration, SCIMPropagationConsumer<GroupScimService> operationToDispatch) {
|
||||
initializeClientsIfNeeded();
|
||||
// Scim client should already have been created
|
||||
Optional<GroupScimService> 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 <T> An {@link AbstractScimService to call}
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SCIMPropagationConsumer<T> {
|
||||
|
||||
void acceptThrows(T elem) throws ScimPropagationException;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue