diff --git a/README.md b/README.md index a358832..151f1c4 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,10 @@ The library is highly configurable. You configure it with properties specified ### For Activiti App Only -| Property | Default | Description | -| ----------------------------------------- | -------------- | ----------- | -| `keycloak-ext.syncGroupAs` | `organization` | When creating a new group, should it be a functional (`organization`) group or a system (`capability`) group? | -| `keycloak-ext.external.id` | `ais` | When creating a new group or registering an internal group as external, use this ID as a prefix to the external group ID. | +| Property | Default | Description | +| ------------------------------------------------ | ------- | ----------- | +| `keycloak-ext.group.organization.regex.patterns` | `.*` | When creating a new group, sync as APS Organization (functional group) when the role matches the specified regular expression. If it doesn't, add as APS Capability (system group). | +| `keycloak-ext.external.id` | `ais` | When creating a new group or registering an internal group as external, use this ID as a prefix to the external group ID. | ### Rare diff --git a/pom.xml b/pom.xml index 8f57c21..5a0a69c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.inteligr8.activiti keycloak-activiti-app-ext - 1.2-SNAPSHOT + 1.3-SNAPSHOT Keycloak Authentication & Authorization for APS diff --git a/src/main/java/com/inteligr8/activiti/keycloak/AbstractKeycloakActivitiAuthenticator.java b/src/main/java/com/inteligr8/activiti/keycloak/AbstractKeycloakActivitiAuthenticator.java index 9610660..935cd2b 100644 --- a/src/main/java/com/inteligr8/activiti/keycloak/AbstractKeycloakActivitiAuthenticator.java +++ b/src/main/java/com/inteligr8/activiti/keycloak/AbstractKeycloakActivitiAuthenticator.java @@ -12,6 +12,8 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.OverridingMethodsMustInvokeSuper; + import org.apache.commons.lang3.StringUtils; import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakSecurityContext; @@ -71,6 +73,7 @@ public abstract class AbstractKeycloakActivitiAuthenticator implements Authentic protected final Set groupExcludes = new HashSet<>(); @Override + @OverridingMethodsMustInvokeSuper public void afterPropertiesSet() { if (this.regexPatterns != null) { String[] regexPatternStrs = StringUtils.split(this.regexPatterns, ','); diff --git a/src/main/java/com/inteligr8/activiti/keycloak/KeycloakActivitiAppAuthenticator.java b/src/main/java/com/inteligr8/activiti/keycloak/KeycloakActivitiAppAuthenticator.java index 94705dc..ea8220a 100644 --- a/src/main/java/com/inteligr8/activiti/keycloak/KeycloakActivitiAppAuthenticator.java +++ b/src/main/java/com/inteligr8/activiti/keycloak/KeycloakActivitiAppAuthenticator.java @@ -1,12 +1,15 @@ package com.inteligr8.activiti.keycloak; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.OverridingMethodsMustInvokeSuper; import javax.persistence.NonUniqueResultException; import org.apache.commons.lang3.StringUtils; @@ -59,16 +62,22 @@ public class KeycloakActivitiAppAuthenticator extends AbstractKeycloakActivitiAu @Value("${keycloak-ext.external.id:ais}") protected String externalIdmSource; - @Value("${keycloak-ext.syncGroupAs:organization}") - protected String syncGroupAs; + @Value("${keycloak-ext.group.organization.regex.patterns:.*}") + protected String regexOrgIncludes; + + protected final Set orgIncludes = new HashSet<>(); - protected boolean syncGroupAsOrganization() { - return !this.syncGroupAsCapability(); - } - - protected boolean syncGroupAsCapability() { - return this.syncGroupAs != null && this.syncGroupAs.toLowerCase().startsWith("cap"); - } + @Override + @OverridingMethodsMustInvokeSuper + public void afterPropertiesSet() { + super.afterPropertiesSet(); + + if (this.regexOrgIncludes != null) { + String[] regexPatternStrs = StringUtils.split(this.regexOrgIncludes, ','); + for (int i = 0; i < regexPatternStrs.length; i++) + this.orgIncludes.add(Pattern.compile(regexPatternStrs[i])); + } + } /** * This method validates that the user exists, if not, it creates the @@ -163,8 +172,6 @@ public class KeycloakActivitiAppAuthenticator extends AbstractKeycloakActivitiAu return; } - boolean syncAsOrg = this.syncGroupAsOrganization(); - // check Activiti groups User userWithGroups = this.userService.getUser(user.getId(), true); for (Group group : userWithGroups.getGroups()) { @@ -228,6 +235,8 @@ public class KeycloakActivitiAppAuthenticator extends AbstractKeycloakActivitiAu if (group == null) { if (this.createMissingGroup) { this.logger.trace("Creating new group for role: {}", role); + boolean syncAsOrg = this.isRoleToBeOrganization(role.getKey()); + this.logger.trace("Creating new group as {}: {}", syncAsOrg ? "organization" : "capability", role); String name = this.keycloakRoleToApsGroupName(role.getValue()); String externalId = this.keycloakRoleToApsGroupExternalId(role.getKey()); int type = syncAsOrg ? Group.TYPE_FUNCTIONAL_GROUP : Group.TYPE_SYSTEM_GROUP; @@ -264,4 +273,17 @@ public class KeycloakActivitiAppAuthenticator extends AbstractKeycloakActivitiAu return externalId; } + private boolean isRoleToBeOrganization(String role) { + if (this.orgIncludes.isEmpty()) + return false; + + for (Pattern regex : this.orgIncludes) { + Matcher matcher = regex.matcher(role); + if (matcher.matches()) + return true; + } + + return false; + } + }