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);
|
this.scimClient = ScimClient.open(scimProviderConfiguration, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void create(RMM roleMapperModel) {
|
public void create(RMM roleMapperModel) throws ScimPropagationException {
|
||||||
boolean skip = isSkip(roleMapperModel);
|
boolean skip = isSkip(roleMapperModel);
|
||||||
if (skip)
|
if (skip)
|
||||||
return;
|
return;
|
||||||
|
@ -71,7 +71,7 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
|
||||||
return keycloakSession;
|
return keycloakSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void replace(RMM roleMapperModel) {
|
public void replace(RMM roleMapperModel) throws ScimPropagationException {
|
||||||
try {
|
try {
|
||||||
if (isSkip(roleMapperModel))
|
if (isSkip(roleMapperModel))
|
||||||
return;
|
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));
|
LOGGER.warnf("failed to replace resource %s, scim mapping not found", getId(roleMapperModel));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error(e);
|
LOGGER.error(e);
|
||||||
|
throw new ScimPropagationException("[SCIM] Error while replacing SCIM resource", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract S toScimForReplace(RMM roleMapperModel, EntityOnRemoteScimId externalId);
|
protected abstract S toScimForReplace(RMM roleMapperModel, EntityOnRemoteScimId externalId);
|
||||||
|
|
||||||
public void delete(KeycloakId id) {
|
public void delete(KeycloakId id) throws ScimPropagationException {
|
||||||
try {
|
try {
|
||||||
ScimResource resource = findById(id).get();
|
ScimResource resource = findById(id).get();
|
||||||
EntityOnRemoteScimId externalId = resource.getExternalIdAsEntityOnRemoteScimId();
|
EntityOnRemoteScimId externalId = resource.getExternalIdAsEntityOnRemoteScimId();
|
||||||
scimClient.delete(externalId);
|
scimClient.delete(externalId);
|
||||||
getScimResourceDao().delete(resource);
|
getScimResourceDao().delete(resource);
|
||||||
} catch (NoSuchElementException e) {
|
} 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");
|
LOGGER.info("Refresh resources");
|
||||||
getResourceStream().forEach(resource -> {
|
getResourceStream().forEach(resource -> {
|
||||||
KeycloakId id = getId(resource);
|
KeycloakId id = getId(resource);
|
||||||
LOGGER.infof("Reconciling local resource %s", id);
|
LOGGER.infof("Reconciling local resource %s", id);
|
||||||
if (!isSkipRefresh(resource)) {
|
if (!isSkipRefresh(resource)) {
|
||||||
try {
|
try {
|
||||||
findById(id).get();
|
try {
|
||||||
LOGGER.info("Replacing it");
|
findById(id).get();
|
||||||
replace(resource);
|
LOGGER.info("Replacing it");
|
||||||
} catch (NoSuchElementException e) {
|
replace(resource);
|
||||||
LOGGER.info("Creating it");
|
} catch (NoSuchElementException e) {
|
||||||
create(resource);
|
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);
|
protected abstract boolean entityExists(KeycloakId keycloakId);
|
||||||
|
|
||||||
public void sync(SynchronizationResult syncRes) {
|
public void sync(SynchronizationResult syncRes) throws ScimPropagationException {
|
||||||
if (this.scimProviderConfiguration.isSyncImport()) {
|
if (this.scimProviderConfiguration.isSyncImport()) {
|
||||||
this.importResources(syncRes);
|
this.importResources(syncRes);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class ScimClient<S extends ResourceNode> implements AutoCloseable {
|
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;
|
private final RetryRegistry retryRegistry;
|
||||||
|
|
||||||
|
@ -83,8 +83,8 @@ public class ScimClient<S extends ResourceNode> implements AutoCloseable {
|
||||||
|
|
||||||
private void checkResponseIsSuccess(ServerResponse<S> response) {
|
private void checkResponseIsSuccess(ServerResponse<S> response) {
|
||||||
if (!response.isSuccess()) {
|
if (!response.isSuccess()) {
|
||||||
logger.warn(response.getResponseBody());
|
LOGGER.warn(response.getResponseBody());
|
||||||
logger.warn(response.getHttpStatus());
|
LOGGER.warn(response.getHttpStatus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ public class ScimClient<S extends ResourceNode> implements AutoCloseable {
|
||||||
|
|
||||||
public void replace(EntityOnRemoteScimId externalId, S scimForReplace) {
|
public void replace(EntityOnRemoteScimId externalId, S scimForReplace) {
|
||||||
Retry retry = retryRegistry.retry("replace-%s".formatted(externalId.asString()));
|
Retry retry = retryRegistry.retry("replace-%s".formatted(externalId.asString()));
|
||||||
logger.warn(scimForReplace);
|
LOGGER.warn(scimForReplace);
|
||||||
ServerResponse<S> response = retry.executeSupplier(() -> scimRequestBuilder
|
ServerResponse<S> response = retry.executeSupplier(() -> scimRequestBuilder
|
||||||
.update(getResourceClass(), getScimEndpoint(), externalId.asString())
|
.update(getResourceClass(), getScimEndpoint(), externalId.asString())
|
||||||
.setResource(scimForReplace)
|
.setResource(scimForReplace)
|
||||||
|
|
|
@ -6,9 +6,10 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import sh.libre.scim.storage.ScimEndpointConfigurationStorageProviderFactory;
|
import sh.libre.scim.storage.ScimEndpointConfigurationStorageProviderFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In charge of sending SCIM Request to all registered Scim endpoints.
|
* 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();
|
initializeClientsIfNeeded();
|
||||||
userScimServices.forEach(operationToDispatch);
|
Set<UserScimService> servicesCorrectlyPropagated = new LinkedHashSet<>();
|
||||||
logger.infof("[SCIM] User operation dispatched to %d SCIM server", userScimServices.size());
|
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();
|
initializeClientsIfNeeded();
|
||||||
groupScimServices.forEach(operationToDispatch);
|
Set<GroupScimService> servicesCorrectlyPropagated = new LinkedHashSet<>();
|
||||||
logger.infof("[SCIM] Group operation dispatched to %d SCIM server", groupScimServices.size());
|
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();
|
initializeClientsIfNeeded();
|
||||||
// Scim client should already have been created
|
// Scim client should already have been created
|
||||||
Optional<UserScimService> matchingClient = userScimServices.stream().filter(u -> u.getConfiguration().getId().equals(scimServerConfiguration.getId())).findFirst();
|
Optional<UserScimService> matchingClient = userScimServices.stream().filter(u -> u.getConfiguration().getId().equals(scimServerConfiguration.getId())).findFirst();
|
||||||
if (matchingClient.isPresent()) {
|
if (matchingClient.isPresent()) {
|
||||||
operationToDispatch.accept(matchingClient.get());
|
try {
|
||||||
logger.infof("[SCIM] User operation dispatched to SCIM server %s", matchingClient.get().getConfiguration().getId());
|
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 {
|
} 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 endpoint configuration" + scimServerConfiguration.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void dispatchGroupModificationToOne(ComponentModel scimServerConfiguration, Consumer<GroupScimService> operationToDispatch) {
|
public void dispatchGroupModificationToOne(ComponentModel scimServerConfiguration, SCIMPropagationConsumer<GroupScimService> operationToDispatch) {
|
||||||
initializeClientsIfNeeded();
|
initializeClientsIfNeeded();
|
||||||
// Scim client should already have been created
|
// Scim client should already have been created
|
||||||
Optional<GroupScimService> matchingClient = groupScimServices.stream().filter(u -> u.getConfiguration().getId().equals(scimServerConfiguration.getId())).findFirst();
|
Optional<GroupScimService> matchingClient = groupScimServices.stream().filter(u -> u.getConfiguration().getId().equals(scimServerConfiguration.getId())).findFirst();
|
||||||
if (matchingClient.isPresent()) {
|
if (matchingClient.isPresent()) {
|
||||||
operationToDispatch.accept(matchingClient.get());
|
try {
|
||||||
logger.infof("[SCIM] Group operation dispatched to SCIM server %s", matchingClient.get().getConfiguration().getId());
|
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 {
|
} 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 endpoint configuration" + scimServerConfiguration.getId());
|
||||||
}
|
}
|
||||||
|
@ -117,10 +144,27 @@ 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);
|
||||||
|
session.getTransactionManager().rollback();
|
||||||
|
}
|
||||||
|
|
||||||
private void initializeClientsIfNeeded() {
|
private void initializeClientsIfNeeded() {
|
||||||
if (!clientsInitialized) {
|
if (!clientsInitialized) {
|
||||||
clientsInitialized = true;
|
clientsInitialized = true;
|
||||||
refreshActiveScimEndpoints();
|
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;
|
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