Exception Handler - Step 2: logging uniformisation and add //TODOs

This commit is contained in:
Alex Morel 2024-06-25 09:36:20 +02:00
parent 72e597a09c
commit 399dfbd17e
8 changed files with 77 additions and 78 deletions

View file

@ -82,10 +82,12 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
entityOnRemoteScimId
.ifPresentOrElse(
externalId -> doReplace(roleMapperModel, externalId),
() -> LOGGER.warnf("failed to replace resource %s, scim mapping not found", id)
() -> {
// TODO Exception Handling : should we throw a ScimPropagationException here ?
LOGGER.warnf("failed to replace resource %s, scim mapping not found", id);
}
);
} catch (Exception e) {
LOGGER.error(e);
throw new ScimPropagationException("[SCIM] Error while replacing SCIM resource", e);
}
}
@ -106,21 +108,21 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
}
public void refreshResources(SynchronizationResult syncRes) throws ScimPropagationException {
LOGGER.info("Refresh resources");
LOGGER.info("[SCIM] Refresh resources for endpoint " + this.getConfiguration().getEndPoint());
try (Stream<RMM> resourcesStream = getResourceStream()) {
Set<RMM> resources = resourcesStream.collect(Collectors.toUnmodifiableSet());
for (RMM resource : resources) {
KeycloakId id = getId(resource);
LOGGER.infof("Reconciling local resource %s", id);
LOGGER.infof("[SCIM] Reconciling local resource %s", id);
if (isSkipRefresh(resource)) {
LOGGER.infof("Skip local resource %s", id);
LOGGER.infof("[SCIM] Skip local resource %s", id);
continue;
}
if (findById(id).isPresent()) {
LOGGER.info("Replacing it");
LOGGER.info("[SCIM] Replacing it");
replace(resource);
} else {
LOGGER.info("Creating it");
LOGGER.info("[SCIM] Creating it");
create(resource);
}
syncRes.increaseUpdated();
@ -133,11 +135,11 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
protected abstract Stream<RMM> getResourceStream();
public void importResources(SynchronizationResult syncRes) throws ScimPropagationException {
LOGGER.info("Import");
LOGGER.info("[SCIM] Import resources for scim endpoint " + this.getConfiguration().getEndPoint());
try {
for (S resource : scimClient.listResources()) {
try {
LOGGER.infof("Reconciling remote resource %s", resource);
LOGGER.infof("[SCIM] Reconciling remote resource %s", resource);
EntityOnRemoteScimId externalId = resource.getId()
.map(EntityOnRemoteScimId::new)
.orElseThrow(() -> new ScimPropagationException("remote SCIM resource doesn't have an id"));
@ -145,49 +147,50 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
if (optionalMapping.isPresent()) {
ScimResource mapping = optionalMapping.get();
if (entityExists(mapping.getIdAsKeycloakId())) {
LOGGER.info("Valid mapping found, skipping");
LOGGER.info("[SCIM] Valid mapping found, skipping");
continue;
} else {
LOGGER.info("Delete a dangling mapping");
LOGGER.info("[SCIM] Delete a dangling mapping");
getScimResourceDao().delete(mapping);
}
}
Optional<KeycloakId> mapped = tryToMap(resource);
if (mapped.isPresent()) {
LOGGER.info("Matched");
LOGGER.info("[SCIM] Matched");
createMapping(mapped.get(), externalId);
} else {
switch (scimProviderConfiguration.getImportAction()) {
case CREATE_LOCAL:
LOGGER.info("Create local resource");
LOGGER.info("[SCIM] Create local resource");
try {
KeycloakId id = createEntity(resource);
createMapping(id, externalId);
syncRes.increaseAdded();
} catch (Exception e) {
// TODO ExceptionHandling should we stop and throw ScimPropagationException here ?
LOGGER.error(e);
}
break;
case DELETE_REMOTE:
LOGGER.info("Delete remote resource");
LOGGER.info("[SCIM] Delete remote resource");
this.scimClient.delete(externalId);
syncRes.increaseRemoved();
break;
case NOTHING:
LOGGER.info("Import action set to NOTHING");
LOGGER.info("[SCIM] Import action set to NOTHING");
break;
}
}
} catch (Exception e) {
// TODO should we stop and throw ScimPropagationException here ?
// TODO ExceptionHandling should we stop and throw ScimPropagationException here ?
LOGGER.error(e);
e.printStackTrace();
syncRes.increaseFailed();
}
}
} catch (ResponseException e) {
// TODO should we stop and throw ScimPropagationException here ?
// TODO ExceptionHandling should we stop and throw ScimPropagationException here ?
LOGGER.error(e);
e.printStackTrace();
syncRes.increaseFailed();

View file

@ -20,7 +20,7 @@ import java.util.TreeSet;
import java.util.stream.Stream;
public class GroupScimService extends AbstractScimService<GroupModel, Group> {
private final Logger logger = Logger.getLogger(GroupScimService.class);
private final Logger LOGGER = Logger.getLogger(GroupScimService.class);
public GroupScimService(KeycloakSession keycloakSession, ScrimProviderConfiguration scimProviderConfiguration) {
super(keycloakSession, scimProviderConfiguration, ScimResourceType.GROUP);
@ -95,13 +95,13 @@ public class GroupScimService extends AbstractScimService<GroupModel, Group> {
if (optionalGroupMemberMapping.isPresent()) {
ScimResource groupMemberMapping = optionalGroupMemberMapping.get();
EntityOnRemoteScimId externalIdAsEntityOnRemoteScimId = groupMemberMapping.getExternalIdAsEntityOnRemoteScimId();
logger.debugf("found mapping for group member %s as %s", member, externalIdAsEntityOnRemoteScimId);
groupMember.setValue(externalIdAsEntityOnRemoteScimId.asString());
URI ref = getUri(ScimResourceType.USER, externalIdAsEntityOnRemoteScimId);
groupMember.setRef(ref.toString());
group.addMember(groupMember);
} else {
logger.warnf("member %s not found for group %s", member, groupModel.getId());
// TODO Exception Handling : should we throw an exception when some group members can't be found ?
LOGGER.warnf("member %s not found for group %s", member, groupModel.getId());
}
}
return group;

View file

@ -85,6 +85,7 @@ public class ScimClient<S extends ResourceNode> implements AutoCloseable {
private void checkResponseIsSuccess(ServerResponse<S> response) {
if (!response.isSuccess()) {
// TODO Exception handling : we should throw a SCIM Progagation exception here
LOGGER.warn("[SCIM] Issue on SCIM Server response ");
LOGGER.warn(response.getResponseBody());
LOGGER.warn(response.getHttpStatus());

View file

@ -16,7 +16,7 @@ import java.util.Set;
*/
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 ScimExceptionHandler exceptionHandler;
@ -34,42 +34,32 @@ public class ScimDispatcher {
* Lists all active ScimStorageProviderFactory and create new ScimClients for each of them
*/
public void refreshActiveScimEndpoints() {
try {
// Step 1: close existing clients
for (GroupScimService c : groupScimServices) {
c.close();
}
groupScimServices.clear();
for (UserScimService c : userScimServices) {
c.close();
}
userScimServices.clear();
// Step 1: close existing clients (as configuration may have changed)
groupScimServices.forEach(GroupScimService::close);
groupScimServices.clear();
userScimServices.forEach(UserScimService::close);
userScimServices.clear();
// Step 2: Get All SCIM endpoints defined in Admin Console (enabled ScimStorageProviderFactory)
session.getContext().getRealm().getComponentsStream()
.filter(m -> ScimEndpointConfigurationStorageProviderFactory.ID.equals(m.getProviderId())
&& m.get("enabled", true))
.forEach(scimEndpointConfigurationRaw -> {
ScrimProviderConfiguration scrimProviderConfiguration = new ScrimProviderConfiguration(scimEndpointConfigurationRaw);
try {
// Step 3 : create scim clients for each endpoint
if (scimEndpointConfigurationRaw.get(ScrimProviderConfiguration.CONF_KEY_PROPAGATION_GROUP, false)) {
GroupScimService groupScimService = new GroupScimService(session, scrimProviderConfiguration);
groupScimServices.add(groupScimService);
}
if (scimEndpointConfigurationRaw.get(ScrimProviderConfiguration.CONF_KEY_PROPAGATION_USER, false)) {
UserScimService userScimService = new UserScimService(session, scrimProviderConfiguration);
userScimServices.add(userScimService);
}
} catch (Exception e) {
logger.warnf("[SCIM] Invalid Endpoint configuration %s: %s", scimEndpointConfigurationRaw.getId(), e.getMessage());
// TODO is it ok to log and try to create the other clients ?
// Step 2: Get All SCIM endpoints defined in Admin Console (enabled ScimStorageProviderFactory)
session.getContext().getRealm().getComponentsStream()
.filter(m -> ScimEndpointConfigurationStorageProviderFactory.ID.equals(m.getProviderId())
&& m.get("enabled", true))
.forEach(scimEndpointConfigurationRaw -> {
ScrimProviderConfiguration scrimProviderConfiguration = new ScrimProviderConfiguration(scimEndpointConfigurationRaw);
try {
// Step 3 : create scim clients for each endpoint
if (scimEndpointConfigurationRaw.get(ScrimProviderConfiguration.CONF_KEY_PROPAGATION_GROUP, false)) {
GroupScimService groupScimService = new GroupScimService(session, scrimProviderConfiguration);
groupScimServices.add(groupScimService);
}
});
} catch (Exception e) {
logger.error("[SCIM] Error while refreshing scim clients ", e);
// TODO : how to handle exception here ?
}
if (scimEndpointConfigurationRaw.get(ScrimProviderConfiguration.CONF_KEY_PROPAGATION_USER, false)) {
UserScimService userScimService = new UserScimService(session, scrimProviderConfiguration);
userScimServices.add(userScimService);
}
} catch (Exception e) {
exceptionHandler.handleInvalidEndpointConfiguration(scimEndpointConfigurationRaw, e);
}
});
}
public void dispatchUserModificationToAll(SCIMPropagationConsumer<UserScimService> operationToDispatch) {
@ -84,7 +74,7 @@ public class ScimDispatcher {
}
});
// TODO we could iterate on servicesCorrectlyPropagated to undo modification
logger.infof("[SCIM] User operation dispatched to %d SCIM server", servicesCorrectlyPropagated.size());
LOGGER.infof("[SCIM] User operation dispatched to %d SCIM server", servicesCorrectlyPropagated.size());
}
public void dispatchGroupModificationToAll(SCIMPropagationConsumer<GroupScimService> operationToDispatch) {
@ -99,7 +89,7 @@ public class ScimDispatcher {
}
});
// TODO we could iterate on servicesCorrectlyPropagated to undo modification
logger.infof("[SCIM] Group operation dispatched to %d SCIM server", servicesCorrectlyPropagated.size());
LOGGER.infof("[SCIM] Group operation dispatched to %d SCIM server", servicesCorrectlyPropagated.size());
}
public void dispatchUserModificationToOne(ComponentModel scimServerConfiguration, SCIMPropagationConsumer<UserScimService> operationToDispatch) {
@ -109,12 +99,12 @@ public class ScimDispatcher {
if (matchingClient.isPresent()) {
try {
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) {
exceptionHandler.handleException(matchingClient.get().getConfiguration(), e);
}
} else {
logger.error("[SCIM] Could not find a Scim Client matching User endpoint configuration" + scimServerConfiguration.getId());
LOGGER.error("[SCIM] Could not find a Scim Client matching User endpoint configuration" + scimServerConfiguration.getId());
}
}
@ -126,12 +116,12 @@ public class ScimDispatcher {
if (matchingClient.isPresent()) {
try {
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) {
exceptionHandler.handleException(matchingClient.get().getConfiguration(), e);
}
} else {
logger.error("[SCIM] Could not find a Scim Client matching Group endpoint configuration" + scimServerConfiguration.getId());
LOGGER.error("[SCIM] Could not find a Scim Client matching Group endpoint configuration" + scimServerConfiguration.getId());
}
}

View file

@ -1,6 +1,7 @@
package sh.libre.scim.core;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
/**
@ -10,8 +11,8 @@ import org.keycloak.models.KeycloakSession;
* - The thrown exception itself
*/
public class ScimExceptionHandler {
private static final Logger LOGGER = Logger.getLogger(ScimExceptionHandler.class);
private static final Logger logger = Logger.getLogger(ScimDispatcher.class);
private final KeycloakSession session;
public ScimExceptionHandler(KeycloakSession session) {
@ -25,7 +26,14 @@ public class ScimExceptionHandler {
* @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();
LOGGER.error("[SCIM] Error while propagating to SCIM endpoint %s", scimProviderConfiguration.getId(), e);
// TODO Exception Handling : rollback only for critical operations, if configuration says so
// session.getTransactionManager().rollback();
}
public void handleInvalidEndpointConfiguration(ComponentModel scimEndpointConfigurationRaw, Exception e) {
LOGGER.error("[SCIM] Invalid Endpoint configuration " + scimEndpointConfigurationRaw.getId(), e);
// TODO Exception Handling is it ok to ignore an invalid Scim endpoint Configuration ?
// IF not, we should propagate the exception here
}
}

View file

@ -20,7 +20,7 @@ import java.util.Optional;
import java.util.stream.Stream;
public class UserScimService extends AbstractScimService<UserModel, User> {
private final Logger logger = Logger.getLogger(UserScimService.class);
private final Logger LOGGER = Logger.getLogger(UserScimService.class);
public UserScimService(
KeycloakSession keycloakSession,
@ -41,17 +41,18 @@ public class UserScimService extends AbstractScimService<UserModel, User> {
@Override
protected Optional<KeycloakId> tryToMap(User resource) {
Optional<KeycloakId> matchedByUsername = resource.getUserName()
.map(getKeycloakDao()::getUserByUsername)
.map(this::getId);
.map(getKeycloakDao()::getUserByUsername)
.map(this::getId);
Optional<KeycloakId> matchedByEmail = resource.getEmails().stream()
.findFirst()
.flatMap(MultiComplexNode::getValue)
.map(getKeycloakDao()::getUserByEmail)
.map(this::getId);
if (matchedByUsername.isPresent()
&& matchedByEmail.isPresent()
&& !matchedByUsername.equals(matchedByEmail)) {
logger.warnf("found 2 possible users for remote user %s %s", matchedByUsername.get(), matchedByEmail.get());
&& matchedByEmail.isPresent()
&& !matchedByUsername.equals(matchedByEmail)) {
// TODO Exception Handling : what should we do here ?
LOGGER.warnf("found 2 possible users for remote user %s %s", matchedByUsername.get(), matchedByEmail.get());
return Optional.empty();
}
if (matchedByUsername.isPresent()) {

View file

@ -228,11 +228,7 @@ public class ScimEventListenerProvider implements EventListenerProvider {
@Override
public void close() {
try {
dispatcher.close();
} catch (Exception e) {
LOGGER.error("Error while closing dispatcher", e);
}
dispatcher.close();
}

View file

@ -31,7 +31,7 @@ import java.util.List;
public class ScimEndpointConfigurationStorageProviderFactory
implements UserStorageProviderFactory<ScimEndpointConfigurationStorageProviderFactory.ScimEndpointConfigurationStorageProvider>, ImportSynchronization {
public static final String ID = "scim";
private final Logger logger = Logger.getLogger(ScimEndpointConfigurationStorageProviderFactory.class);
private final Logger LOGGER = Logger.getLogger(ScimEndpointConfigurationStorageProviderFactory.class);
@Override
public String getId() {
@ -43,7 +43,7 @@ public class ScimEndpointConfigurationStorageProviderFactory
public SynchronizationResult sync(KeycloakSessionFactory sessionFactory, String realmId,
UserStorageProviderModel model) {
// TODO if this should be kept here, better document purpose & usage
logger.infof("[SCIM] Sync from ScimStorageProvider - Realm %s - Model %s", realmId, model.getId());
LOGGER.infof("[SCIM] Sync from ScimStorageProvider - Realm %s - Model %s", realmId, model.getId());
SynchronizationResult result = new SynchronizationResult();
KeycloakModelUtils.runJobInTransaction(sessionFactory, session -> {
RealmModel realm = session.realms().getRealm(realmId);
@ -78,7 +78,7 @@ public class ScimEndpointConfigurationStorageProviderFactory
ScimDispatcher dispatcher = new ScimDispatcher(session);
for (GroupModel group : session.groups().getGroupsStream(realm)
.filter(x -> BooleanUtils.TRUE.equals(x.getFirstAttribute("scim-dirty"))).toList()) {
logger.infof("[SCIM] Dirty group: %s", group.getName());
LOGGER.infof("[SCIM] Dirty group: %s", group.getName());
dispatcher.dispatchGroupModificationToAll(client -> client.replace(group));
group.removeAttribute("scim-dirty");
}