Exception Handler - Step 2: logging uniformisation and add //TODOs
This commit is contained in:
parent
72e597a09c
commit
399dfbd17e
8 changed files with 77 additions and 78 deletions
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue