From 764767185e5682b630214ab68216c582baa41abf Mon Sep 17 00:00:00 2001 From: Alex Morel Date: Tue, 18 Jun 2024 16:07:54 +0200 Subject: [PATCH] Simply ScimEventListener code --- .../scim/event/ScimEventListenerProvider.java | 150 ++++++++++++------ .../ScimEventListenerProviderFactory.java | 12 +- 2 files changed, 109 insertions(+), 53 deletions(-) diff --git a/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java b/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java index 737e0819d4..ef68c552e7 100644 --- a/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java +++ b/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java @@ -16,6 +16,11 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * An {@link java.util.EventListener} in charge of reaction to Keycloak models + * modification (e.g. User creation, Group deletion, membership modifications...) + * by propagating it to all registered SCIM servers. + */ public class ScimEventListenerProvider implements EventListenerProvider { private static final Logger LOGGER = Logger.getLogger(ScimEventListenerProvider.class); @@ -36,14 +41,13 @@ public class ScimEventListenerProvider implements EventListenerProvider { dispatcher = new ScimDispatcher(session); } - @Override - public void close() { - } @Override public void onEvent(Event event) { + // React to User-related event : creation, deletion, update EventType eventType = event.getType(); String eventUserId = event.getUserId(); + LOGGER.infof("[SCIM] Propagate User Event %s - %s", eventType, eventUserId); switch (eventType) { case REGISTER -> { UserModel user = getUser(eventUserId); @@ -54,81 +58,123 @@ public class ScimEventListenerProvider implements EventListenerProvider { dispatcher.dispatchUserModificationToAll(client -> client.replace(user)); } case DELETE_ACCOUNT -> dispatcher.dispatchUserModificationToAll(client -> client.delete(eventUserId)); - default -> LOGGER.trace("ignore event " + eventType); + default -> { + // No other event has to be propagated to SCIM Servers + } } } @Override public void onEvent(AdminEvent event, boolean includeRepresentation) { + // Step 1: check if event is relevant for propagation through SCIM Pattern pattern = patterns.get(event.getResourceType()); if (pattern == null) return; Matcher matcher = pattern.matcher(event.getResourcePath()); if (!matcher.find()) return; + + // Step 2: propagate event (if needed) according to its resource type switch (event.getResourceType()) { case USER -> { String userId = matcher.group(1); - LOGGER.infof("%s %s", userId, event.getOperationType()); - switch (event.getOperationType()) { - case CREATE -> { - UserModel user = getUser(userId); - dispatcher.dispatchUserModificationToAll(client -> client.create(user)); - user.getGroupsStream().forEach(group -> { - dispatcher.dispatchGroupModificationToAll(client -> client.replace(group)); - }); - } - case UPDATE -> { - UserModel user = getUser(userId); - dispatcher.dispatchUserModificationToAll(client -> client.replace(user)); - } - case DELETE -> dispatcher.dispatchUserModificationToAll(client -> client.delete(userId)); - } + handleUserEvent(event, userId); } case GROUP -> { String groupId = matcher.group(1); - LOGGER.infof("group %s %s", groupId, event.getOperationType()); - switch (event.getOperationType()) { - case CREATE -> { - GroupModel group = getGroup(groupId); - dispatcher.dispatchGroupModificationToAll(client -> client.create(group)); - } - case UPDATE -> { - GroupModel group = getGroup(groupId); - dispatcher.dispatchGroupModificationToAll(client -> client.replace(group)); - } - case DELETE -> dispatcher.dispatchGroupModificationToAll(client -> client.delete(groupId)); - } + handleGroupEvent(event, groupId); } case GROUP_MEMBERSHIP -> { String userId = matcher.group(1); String groupId = matcher.group(2); - LOGGER.infof("%s %s from %s", event.getOperationType(), userId, groupId); - GroupModel group = getGroup(groupId); - group.setSingleAttribute("scim-dirty", BooleanUtils.TRUE); - UserModel user = getUser(userId); - dispatcher.dispatchUserModificationToAll(client -> client.replace(user)); + handleGroupMemberShipEvent(event, userId, groupId); } case REALM_ROLE_MAPPING -> { String type = matcher.group(1); String id = matcher.group(2); - LOGGER.infof("%s %s %s roles", event.getOperationType(), type, id); - switch (type) { - case "users" -> { - UserModel user = getUser(id); - dispatcher.dispatchUserModificationToAll(client -> client.replace(user)); - } - case "groups" -> { - GroupModel group = getGroup(id); - session.users().getGroupMembersStream(session.getContext().getRealm(), group).forEach(user -> { - dispatcher.dispatchUserModificationToAll(client -> client.replace(user)); - }); - } - } + handleRoleMappingEvent(event, type, id); + } + default -> { + // No other resource modification has to be propagated to SCIM Servers } } } + private void handleUserEvent(AdminEvent userEvent, String userId) { + LOGGER.infof("[SCIM] Propagate User %s - %s", userEvent.getOperationType(), userId); + switch (userEvent.getOperationType()) { + case CREATE -> { + UserModel user = getUser(userId); + dispatcher.dispatchUserModificationToAll(client -> client.create(user)); + user.getGroupsStream().forEach(group -> + dispatcher.dispatchGroupModificationToAll(client -> client.replace(group) + )); + } + case UPDATE -> { + UserModel user = getUser(userId); + dispatcher.dispatchUserModificationToAll(client -> client.replace(user)); + } + case DELETE -> dispatcher.dispatchUserModificationToAll(client -> client.delete(userId)); + default -> { + // ACTION userEvent are not relevant, nothing to do + } + } + } + + /** + * Propagating the given group-related event to SCIM servers. + * + * @param event the event to propagate + * @param groupId event target's id + */ + private void handleGroupEvent(AdminEvent event, String groupId) { + LOGGER.infof("[SCIM] Propagate Group %s - %s", event.getOperationType(), groupId); + switch (event.getOperationType()) { + case CREATE -> { + GroupModel group = getGroup(groupId); + dispatcher.dispatchGroupModificationToAll(client -> client.create(group)); + } + case UPDATE -> { + GroupModel group = getGroup(groupId); + dispatcher.dispatchGroupModificationToAll(client -> client.replace(group)); + } + case DELETE -> dispatcher.dispatchGroupModificationToAll(client -> client.delete(groupId)); + default -> { + // ACTION event are not relevant, nothing to do + } + } + } + + private void handleGroupMemberShipEvent(AdminEvent groupMemberShipEvent, String userId, String groupId) { + LOGGER.infof("[SCIM] Propagate GroupMemberShip %s - User %s Group %s", groupMemberShipEvent.getOperationType(), userId, groupId); + GroupModel group = getGroup(groupId); + group.setSingleAttribute("scim-dirty", BooleanUtils.TRUE); + UserModel user = getUser(userId); + dispatcher.dispatchUserModificationToAll(client -> client.replace(user)); + } + + private void handleRoleMappingEvent(AdminEvent roleMappingEvent, String type, String id) { + LOGGER.infof("[SCIM] Propagate RoleMapping %s - %s %s", roleMappingEvent.getOperationType(), type, id); + switch (type) { + case "users" -> { + UserModel user = getUser(id); + dispatcher.dispatchUserModificationToAll(client -> client.replace(user)); + } + case "groups" -> { + GroupModel group = getGroup(id); + session.users() + .getGroupMembersStream(session.getContext().getRealm(), group) + .forEach(user -> + dispatcher.dispatchUserModificationToAll(client -> client.replace(user) + )); + } + default -> { + // No other type is relevant for propagation + } + } + } + + private UserModel getUser(String id) { return session.users().getUserById(session.getContext().getRealm(), id); } @@ -136,4 +182,10 @@ public class ScimEventListenerProvider implements EventListenerProvider { private GroupModel getGroup(String id) { return session.groups().getGroupById(session.getContext().getRealm(), id); } + + @Override + public void close() { + // Nothing to close here + } + } diff --git a/src/main/java/sh/libre/scim/event/ScimEventListenerProviderFactory.java b/src/main/java/sh/libre/scim/event/ScimEventListenerProviderFactory.java index debe59b8fb..c7b437a287 100644 --- a/src/main/java/sh/libre/scim/event/ScimEventListenerProviderFactory.java +++ b/src/main/java/sh/libre/scim/event/ScimEventListenerProviderFactory.java @@ -13,20 +13,24 @@ public class ScimEventListenerProviderFactory implements EventListenerProviderFa return new ScimEventListenerProvider(session); } + @Override + public String getId() { + return "scim"; + } + @Override public void init(Scope config) { + // Nothing to initialize } @Override public void postInit(KeycloakSessionFactory factory) { + // Nothing to initialize } @Override public void close() { + // Nothing to close } - @Override - public String getId() { - return "scim"; - } }