From f3b70c157400120f243ced5da3f98d9f581f4913 Mon Sep 17 00:00:00 2001 From: Brian Long Date: Fri, 27 Aug 2021 00:22:06 -0400 Subject: [PATCH] refactored tenant handling --- .../activiti/ActivitiAppAdminGroupFixer.java | 128 ++++++++++++++ .../ActivitiAppAdminMembersFixer.java | 86 ++++++++++ .../com/inteligr8/activiti/DataFixer.java | 7 + ...nteligr8SecurityConfigurationRegistry.java | 157 +----------------- .../activiti/TenantFinderService.java | 82 +++++++++ .../KeycloakActivitiAppAuthenticator.java | 32 +--- 6 files changed, 313 insertions(+), 179 deletions(-) create mode 100644 src/main/java/com/inteligr8/activiti/ActivitiAppAdminGroupFixer.java create mode 100644 src/main/java/com/inteligr8/activiti/ActivitiAppAdminMembersFixer.java create mode 100644 src/main/java/com/inteligr8/activiti/DataFixer.java create mode 100644 src/main/java/com/inteligr8/activiti/TenantFinderService.java diff --git a/src/main/java/com/inteligr8/activiti/ActivitiAppAdminGroupFixer.java b/src/main/java/com/inteligr8/activiti/ActivitiAppAdminGroupFixer.java new file mode 100644 index 0000000..e54ff5c --- /dev/null +++ b/src/main/java/com/inteligr8/activiti/ActivitiAppAdminGroupFixer.java @@ -0,0 +1,128 @@ +package com.inteligr8.activiti; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.activiti.domain.idm.Group; +import com.activiti.domain.idm.GroupCapability; +import com.activiti.domain.idm.Tenant; +import com.activiti.service.api.GroupService; + +/** + * This class/bean overrides the APS security configuration with a collection + * of implementations. The OOTB extension only provides one override. This + * uses that extension point, but delegates it out to multiple possible + * implementations. + * + * Order cannot be controlled, so it should not be assumed in any adapter + * implementation. + * + * @author brian@inteligr8.com + */ +@Component +public class ActivitiAppAdminGroupFixer implements DataFixer { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final List adminCapabilities = Arrays.asList( + "access-all-models-in-tenant", + "access-editor", + "access-reports", + "publish-app-to-dashboard", + "tenant-admin", + "tenant-admin-api", + "upload-license"); + + @Autowired(required = false) + private GroupService groupService; + + @Autowired + private TenantFinderService tenantFinderService; + + @Value("${keycloak-ext.group.admins.name:admins}") + private String adminGroupName; + + @Value("${keycloak-ext.group.admins.externalId:#{null}}") + private String adminGroupExternalId; + + @Value("${keycloak-ext.group.admins.validate:false}") + private boolean validateAdministratorsGroup; + + @Override + public void fix() { + this.logger.trace("fix()"); + + if (this.logger.isTraceEnabled()) + this.logGroups(); + if (this.validateAdministratorsGroup) + this.validateAdmins(); + } + + private void logGroups() { + if (this.groupService == null) + return; + + Collection tenants = this.tenantFinderService.getTenants(); + for (Tenant tenant : tenants) { + this.logger.trace("Tenant: {} => {}", tenant.getId(), tenant.getName()); + this.logger.trace("Functional groups: {}", this.toGroupNames(this.groupService.getFunctionalGroups(tenant.getId()))); + this.logger.trace("System groups: {}", this.toGroupNames(this.groupService.getSystemGroups(tenant.getId()))); + } + + this.logger.trace("Tenant: null"); + this.logger.trace("Functional groups: {}", this.toGroupNames(this.groupService.getFunctionalGroups(null))); + this.logger.trace("System groups: {}", this.toGroupNames(this.groupService.getSystemGroups(null))); + } + + private void validateAdmins() { + if (this.groupService == null) + return; + + Long tenantId = this.tenantFinderService.findTenantId(); + Group group = this.groupService.getGroupByExternalIdAndTenantId(this.adminGroupExternalId, tenantId); + if (group == null) { + List groups = this.groupService.getGroupByNameAndTenantId(this.adminGroupName, tenantId); + if (!groups.isEmpty()) + group = groups.iterator().next(); + } + + if (group == null) { + this.logger.info("Creating group: {} ({})", this.adminGroupName, this.adminGroupExternalId); + if (this.adminGroupExternalId != null) { + group = this.groupService.createGroupFromExternalStore( + this.adminGroupExternalId, tenantId, Group.TYPE_SYSTEM_GROUP, null, this.adminGroupName, new Date()); + } else { + group = this.groupService.createGroup(this.adminGroupName, tenantId, Group.TYPE_SYSTEM_GROUP, null); + } + } + + this.logger.debug("Checking group capabilities: {}", group.getName()); + Group groupWithCaps = this.groupService.getGroup(group.getId(), false, true, false, false); + Set adminCaps = new HashSet<>(this.adminCapabilities); + for (GroupCapability cap : groupWithCaps.getCapabilities()) + adminCaps.remove(cap.getName()); + if (!adminCaps.isEmpty()) { + this.logger.info("Granting group '{}' capabilities: {}", group.getName(), adminCaps); + this.groupService.addCapabilitiesToGroup(group.getId(), new ArrayList<>(adminCaps)); + } + } + + private Collection toGroupNames(Collection groups) { + List groupNames = new ArrayList<>(groups.size()); + for (Group group : groups) + groupNames.add(group.getName() + " [" + group.getExternalId() + "]"); + return groupNames; + } + +} diff --git a/src/main/java/com/inteligr8/activiti/ActivitiAppAdminMembersFixer.java b/src/main/java/com/inteligr8/activiti/ActivitiAppAdminMembersFixer.java new file mode 100644 index 0000000..068a7cc --- /dev/null +++ b/src/main/java/com/inteligr8/activiti/ActivitiAppAdminMembersFixer.java @@ -0,0 +1,86 @@ +package com.inteligr8.activiti; + +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.activiti.domain.idm.Group; +import com.activiti.domain.idm.User; +import com.activiti.service.api.GroupService; +import com.activiti.service.api.UserService; + +/** + * This class/bean overrides the APS security configuration with a collection + * of implementations. The OOTB extension only provides one override. This + * uses that extension point, but delegates it out to multiple possible + * implementations. + * + * Order cannot be controlled, so it should not be assumed in any adapter + * implementation. + * + * @author brian@inteligr8.com + */ +@Component +public class ActivitiAppAdminMembersFixer implements DataFixer { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired(required = false) + private UserService userService; + + @Autowired(required = false) + private GroupService groupService; + + @Autowired + private TenantFinderService tenantFinderService; + + @Value("${keycloak-ext.default.admins.users:#{null}}") + private String adminUserStrs; + + @Value("${keycloak-ext.group.admins.name:admins}") + private String adminGroupName; + + @Value("${keycloak-ext.group.admins.externalId:#{null}}") + private String adminGroupExternalId; + + @Override + public void fix() { + this.logger.trace("fix()"); + + if (this.adminUserStrs != null && this.adminUserStrs.length() > 0) + this.associateAdmins(); + } + + private void associateAdmins() { + if (this.userService == null || this.groupService == null) + return; + + List adminUsers = Arrays.asList(this.adminUserStrs.split(",")); + if (adminUsers.isEmpty()) + return; + + Long tenantId = this.tenantFinderService.findTenantId(); + List groups; + Group group1 = this.groupService.getGroupByExternalIdAndTenantId(this.adminGroupExternalId, tenantId); + if (group1 != null) { + groups = Arrays.asList(group1); + } else { + groups = this.groupService.getGroupByNameAndTenantId(this.adminGroupName, tenantId); + } + this.logger.debug("Found {} admin group(s)", groups.size()); + + for (String email : adminUsers) { + User user = this.userService.findUserByEmail(email); + + this.logger.debug("Adding {} to admin group(s)", user.getEmail()); + for (Group group : groups) + this.groupService.addUserToGroup(group, user); + } + } + +} diff --git a/src/main/java/com/inteligr8/activiti/DataFixer.java b/src/main/java/com/inteligr8/activiti/DataFixer.java new file mode 100644 index 0000000..cc4888c --- /dev/null +++ b/src/main/java/com/inteligr8/activiti/DataFixer.java @@ -0,0 +1,7 @@ +package com.inteligr8.activiti; + +public interface DataFixer { + + void fix(); + +} diff --git a/src/main/java/com/inteligr8/activiti/Inteligr8SecurityConfigurationRegistry.java b/src/main/java/com/inteligr8/activiti/Inteligr8SecurityConfigurationRegistry.java index 130bfde..ac80d78 100644 --- a/src/main/java/com/inteligr8/activiti/Inteligr8SecurityConfigurationRegistry.java +++ b/src/main/java/com/inteligr8/activiti/Inteligr8SecurityConfigurationRegistry.java @@ -1,31 +1,16 @@ package com.inteligr8.activiti; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; -import java.util.Date; -import java.util.HashSet; import java.util.List; -import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Component; import com.activiti.api.security.AlfrescoSecurityConfigOverride; -import com.activiti.domain.idm.Group; -import com.activiti.domain.idm.GroupCapability; -import com.activiti.domain.idm.Tenant; -import com.activiti.domain.idm.User; -import com.activiti.service.api.GroupService; -import com.activiti.service.api.UserService; -import com.activiti.service.idm.TenantService; -import com.activiti.service.license.LicenseService; /** * This class/bean overrides the APS security configuration with a collection @@ -43,44 +28,11 @@ public class Inteligr8SecurityConfigurationRegistry implements AlfrescoSecurityC private final Logger logger = LoggerFactory.getLogger(this.getClass()); - private final List adminCapabilities = Arrays.asList( - "access-all-models-in-tenant", - "access-editor", - "access-reports", - "publish-app-to-dashboard", - "tenant-admin", - "tenant-admin-api", - "upload-license"); - @Autowired private List adapters; @Autowired(required = false) - private LicenseService licenseService; - - @Autowired(required = false) - private TenantService tenantService; - - @Autowired(required = false) - private UserService userService; - - @Autowired(required = false) - private GroupService groupService; - - @Value("${keycloak-ext.tenant:#{null}}") - private String tenant; - - @Value("${keycloak-ext.default.admins.users:#{null}}") - private String adminUserStrs; - - @Value("${keycloak-ext.group.admins.name:admins}") - private String adminGroupName; - - @Value("${keycloak-ext.group.admins.externalId:#{null}}") - private String adminGroupExternalId; - - @Value("${keycloak-ext.group.admins.validate:false}") - private boolean validateAdministratorsGroup; + private List fixers; @Override public void configureGlobal(AuthenticationManagerBuilder authmanBuilder, UserDetailsService userDetailsService) { @@ -88,12 +40,10 @@ public class Inteligr8SecurityConfigurationRegistry implements AlfrescoSecurityC Collections.sort(this.adapters); - if (this.logger.isTraceEnabled()) - this.logGroups(); - if (this.validateAdministratorsGroup) - this.validateAdmins(); - if (this.adminUserStrs != null && this.adminUserStrs.length() > 0) - this.associateAdmins(); + if (this.fixers != null) { + for (DataFixer fixer : this.fixers) + fixer.fix(); + } for (ActivitiSecurityConfigAdapter adapter : this.adapters) { if (adapter.isEnabled()) { @@ -105,102 +55,5 @@ public class Inteligr8SecurityConfigurationRegistry implements AlfrescoSecurityC } } } - - private void logGroups() { - if (this.groupService == null) - return; - - List tenantObjs = this.tenantService.getAllTenants(); - for (Object[] tenantObj : tenantObjs) { - Long tenantId = (Long)tenantObj[0]; - if (tenantId != null) { - Tenant tenant = this.tenantService.getTenant(tenantId); - this.logger.trace("Tenant: {} => {}", tenantId, tenant.getName()); - this.logger.trace("Functional groups: {}", this.toGroupNames(this.groupService.getFunctionalGroups(tenantId))); - this.logger.trace("System groups: {}", this.toGroupNames(this.groupService.getSystemGroups(tenantId))); - } - } - } - - private void validateAdmins() { - if (this.groupService == null) - return; - - Long tenantId = this.findTenantId(); - Group group = this.groupService.getGroupByExternalIdAndTenantId(this.adminGroupExternalId, tenantId); - if (group == null) { - List groups = this.groupService.getGroupByNameAndTenantId(this.adminGroupName, tenantId); - if (!groups.isEmpty()) - group = groups.iterator().next(); - } - - if (group == null) { - this.logger.info("Creating group: {} ({})", this.adminGroupName, this.adminGroupExternalId); - if (this.adminGroupExternalId != null) { - group = this.groupService.createGroupFromExternalStore( - this.adminGroupExternalId, tenantId, Group.TYPE_SYSTEM_GROUP, null, this.adminGroupName, new Date()); - } else { - group = this.groupService.createGroup(this.adminGroupName, tenantId, Group.TYPE_SYSTEM_GROUP, null); - } - } - - this.logger.debug("Checking group capabilities: {}", group.getName()); - Group groupWithCaps = this.groupService.getGroup(group.getId(), false, true, false, false); - Set adminCaps = new HashSet<>(this.adminCapabilities); - for (GroupCapability cap : groupWithCaps.getCapabilities()) - adminCaps.remove(cap.getName()); - if (!adminCaps.isEmpty()) { - this.logger.info("Granting group '{}' capabilities: {}", group.getName(), adminCaps); - this.groupService.addCapabilitiesToGroup(group.getId(), new ArrayList<>(adminCaps)); - } - } - - private void associateAdmins() { - if (this.userService == null || this.groupService == null) - return; - - List adminUsers = Arrays.asList(this.adminUserStrs.split(",")); - if (adminUsers.isEmpty()) - return; - - Long tenantId = this.findTenantId(); - List groups; - Group group1 = this.groupService.getGroupByExternalIdAndTenantId(this.adminGroupExternalId, tenantId); - if (group1 != null) { - groups = Arrays.asList(group1); - } else { - groups = this.groupService.getGroupByNameAndTenantId(this.adminGroupName, tenantId); - } - this.logger.debug("Found {} admin group(s)", groups.size()); - - for (String email : adminUsers) { - User user = this.userService.findUserByEmail(email); - - this.logger.debug("Adding {} to admin group(s)", user.getEmail()); - for (Group group : groups) - this.groupService.addUserToGroup(group, user); - } - } - - private Long findTenantId() { - String tenantName = this.tenant == null ? this.licenseService.getDefaultTenantName() : this.tenant; - this.logger.trace("Using Tenant: {}", tenantName); - - List tenants = this.tenantService.findTenantsByName(tenantName); - if (tenants == null || tenants.isEmpty()) { - this.logger.warn("Default tenant not found"); - return null; - } - - Tenant tenant = tenants.iterator().next(); - return tenant.getId(); - } - - private Collection toGroupNames(Collection groups) { - List groupNames = new ArrayList<>(groups.size()); - for (Group group : groups) - groupNames.add(group.getName() + " [" + group.getExternalId() + "]"); - return groupNames; - } } diff --git a/src/main/java/com/inteligr8/activiti/TenantFinderService.java b/src/main/java/com/inteligr8/activiti/TenantFinderService.java new file mode 100644 index 0000000..cbd7604 --- /dev/null +++ b/src/main/java/com/inteligr8/activiti/TenantFinderService.java @@ -0,0 +1,82 @@ +package com.inteligr8.activiti; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.activiti.domain.idm.Tenant; +import com.activiti.service.idm.TenantService; +import com.activiti.service.license.LicenseService; + +@Component +public class TenantFinderService { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired(required = false) + private LicenseService licenseService; + + @Autowired(required = false) + private TenantService tenantService; + + @Value("${keycloak-ext.tenant:#{null}}") + private String tenant; + + public Long findTenantId() { + Tenant tenant = this.findTenant(); + return tenant == null ? null : tenant.getId(); + } + + public Tenant findTenant() { + this.logger.debug("Checking for a single tenant ..."); + + String tenantName = null; + if (this.tenant != null) { + tenantName = this.tenant; + } else { + List tenants = this.tenantService.getAllTenants(); + if (tenants == null || tenants.isEmpty()) { + this.logger.warn("No tenants found!"); + return null; + } else if (tenants.size() == 1) { + Object[] tenant = tenants.iterator().next(); + this.logger.debug("Only one tenant available; selecting it: {}", tenant[0]); + return this.tenantService.getTenant((Long)tenant[0]); + } else { + tenantName = this.licenseService.getDefaultTenantName(); + } + } + + this.logger.debug("Trying to find by tenant name: {}", tenantName); + + List tenants = this.tenantService.findTenantsByName(tenantName); + if (tenants == null || tenants.isEmpty()) { + this.logger.warn("Named tenant not found"); + return null; + } + + this.logger.debug("Found {} tenants with name {}; selecting the first one", tenants.size(), tenantName); + return tenants.iterator().next(); + } + + public Collection getTenants() { + List tenantObjs = this.tenantService.getAllTenants(); + + List tenants = new ArrayList<>(tenantObjs.size()); + for (Object[] tenantObj : tenantObjs) { + if (tenantObj != null && tenantObj[0] != null) { + Tenant tenant = this.tenantService.getTenant((Long)tenantObj[0]); + tenants.add(tenant); + } + } + + return tenants; + } + +} diff --git a/src/main/java/com/inteligr8/activiti/keycloak/KeycloakActivitiAppAuthenticator.java b/src/main/java/com/inteligr8/activiti/keycloak/KeycloakActivitiAppAuthenticator.java index ed3df1f..94705dc 100644 --- a/src/main/java/com/inteligr8/activiti/keycloak/KeycloakActivitiAppAuthenticator.java +++ b/src/main/java/com/inteligr8/activiti/keycloak/KeycloakActivitiAppAuthenticator.java @@ -21,12 +21,10 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; import com.activiti.domain.idm.Group; -import com.activiti.domain.idm.Tenant; import com.activiti.domain.idm.User; import com.activiti.service.api.GroupService; import com.activiti.service.api.UserService; -import com.activiti.service.idm.TenantService; -import com.activiti.service.license.LicenseService; +import com.inteligr8.activiti.TenantFinderService; /** * This class/bean implements an Open ID Connect authenticator for Alfresco @@ -48,12 +46,6 @@ public class KeycloakActivitiAppAuthenticator extends AbstractKeycloakActivitiAu private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Pattern emailNamesPattern = Pattern.compile("([A-Za-z]+)[A-Za-z0-9]*\\.([A-Za-z]+)[A-Za-z0-9]*@.*"); - - @Autowired - private LicenseService licenseService; - - @Autowired - private TenantService tenantService; @Autowired private UserService userService; @@ -61,8 +53,8 @@ public class KeycloakActivitiAppAuthenticator extends AbstractKeycloakActivitiAu @Autowired private GroupService groupService; - @Value("${keycloak-ext.tenant:#{null}}") - private String tenant; + @Autowired + private TenantFinderService tenantFinderService; @Value("${keycloak-ext.external.id:ais}") protected String externalIdmSource; @@ -84,7 +76,7 @@ public class KeycloakActivitiAppAuthenticator extends AbstractKeycloakActivitiAu */ @Override public void preAuthenticate(Authentication auth) throws AuthenticationException { - Long tenantId = this.findTenantId(); + Long tenantId = this.tenantFinderService.findTenantId(); this.logger.trace("Tenant ID: {}", tenantId); User user = this.findUser(auth, tenantId); @@ -125,27 +117,13 @@ public class KeycloakActivitiAppAuthenticator extends AbstractKeycloakActivitiAu */ @Override public void postAuthenticate(Authentication auth) throws AuthenticationException { - Long tenantId = this.findTenantId(); + Long tenantId = this.tenantFinderService.findTenantId(); User user = this.findUser(auth, tenantId); this.logger.debug("Inspecting user: {} => {}", user.getId(), user.getExternalId()); this.syncUserRoles(user, auth, tenantId); } - private Long findTenantId() { - String tenantName = this.tenant == null ? this.licenseService.getDefaultTenantName() : this.tenant; - this.logger.trace("Using Tenant: {}", tenantName); - - List tenants = this.tenantService.findTenantsByName(tenantName); - if (tenants == null || tenants.isEmpty()) { - this.logger.warn("Tenant not found: {}", tenantName); - return null; - } - - Tenant tenant = tenants.iterator().next(); - return tenant.getId(); - } - private User findUser(Authentication auth, Long tenantId) { String email = auth.getName();