commit 5db42ccbc8907c911def9c67c27e387bf93573bf Author: Brian Long Date: Fri Jul 30 15:37:05 2021 -0400 initial checkin diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5fbee4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Maven +target +pom.xml.versionsBackup + +# Eclipse +.project +.classpath +.settings + +# Visual Studio Code +.factorypath diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4872245 --- /dev/null +++ b/pom.xml @@ -0,0 +1,65 @@ + + 4.0.0 + com.inteligr8.activiti + oidc-activiti-app-ext + 1.0-SNAPSHOT + Keycloak Authentication & Authorization for APS + + + 11 + 11 + 11 + + 1.11.1.1 + 6.0.1 + 2.0.17.RELEASE + 1.7.26 + + + + + org.slf4j + slf4j-api + ${slf4j.version} + provided + + + org.springframework.security.oauth + spring-security-oauth2 + ${spring-security-oauth2.version} + provided + + + org.keycloak + keycloak-spring-security-adapter + ${keycloak.version} + provided + + + com.activiti + activiti-app + ${aps.version} + classes + provided + + + com.activiti + activiti-app-logic + ${aps.version} + provided + + + + + + alfresco-public + https://artifacts.alfresco.com/nexus/content/repositories/public + + + activiti-releases + https://artifacts.alfresco.com/nexus/content/repositories/activiti-enterprise-releases + + + \ No newline at end of file diff --git a/src/main/java/com/activiti/extension/conf/OidcExtSpringComponentScanner.java b/src/main/java/com/activiti/extension/conf/OidcExtSpringComponentScanner.java new file mode 100644 index 0000000..b53a694 --- /dev/null +++ b/src/main/java/com/activiti/extension/conf/OidcExtSpringComponentScanner.java @@ -0,0 +1,10 @@ +package com.activiti.extension.conf; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan(basePackages = {"com.inteligr8.activiti.oidc"}) +public class OidcExtSpringComponentScanner { + +} diff --git a/src/main/java/com/inteligr8/activiti/ais/AbstractIdentityServiceActivitiAuthenticator.java b/src/main/java/com/inteligr8/activiti/ais/AbstractIdentityServiceActivitiAuthenticator.java new file mode 100644 index 0000000..dc8426e --- /dev/null +++ b/src/main/java/com/inteligr8/activiti/ais/AbstractIdentityServiceActivitiAuthenticator.java @@ -0,0 +1,54 @@ +package com.inteligr8.activiti.ais; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.keycloak.KeycloakPrincipal; +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.adapters.OidcKeycloakAccount; +import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; +import org.keycloak.representations.AccessToken; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import com.activiti.security.identity.service.authentication.provider.IdentityServiceAuthenticationToken; + +public abstract class AbstractIdentityServiceActivitiAuthenticator implements Authenticator { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + + + protected AccessToken getOidcAccessToken(Authentication auth) { + KeycloakSecurityContext ksc = this.getKeycloakSecurityContext(auth); + return ksc.getToken(); + } + + @SuppressWarnings("unchecked") + protected KeycloakSecurityContext getKeycloakSecurityContext(Authentication auth) { + if (auth instanceof KeycloakAuthenticationToken) { + this.logger.debug("Fetching KeycloakSecurityContext from KeycloakAuthenticationToken"); + if (auth.getPrincipal() instanceof KeycloakPrincipal) { + return ((KeycloakPrincipal)auth.getPrincipal()).getKeycloakSecurityContext(); + } else { + return null; + } + } else if (auth instanceof IdentityServiceAuthenticationToken) { + this.logger.debug("Fetching KeycloakSecurityContext from IdentityServiceAuthenticationToken"); + OidcKeycloakAccount account = ((IdentityServiceAuthenticationToken)auth).getAccount(); + return account == null ? null : account.getKeycloakSecurityContext(); + } else { + return null; + } + } + + protected Set toSet(Collection grantedAuthorities) { + Set authorities = new HashSet<>(grantedAuthorities.size()); + for (GrantedAuthority grantedAuthority : grantedAuthorities) + authorities.add(grantedAuthority.getAuthority()); + return authorities; + } +} diff --git a/src/main/java/com/inteligr8/activiti/ais/Authenticator.java b/src/main/java/com/inteligr8/activiti/ais/Authenticator.java new file mode 100644 index 0000000..cfa145f --- /dev/null +++ b/src/main/java/com/inteligr8/activiti/ais/Authenticator.java @@ -0,0 +1,14 @@ +package com.inteligr8.activiti.ais; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +public interface Authenticator { + + default void preAuthenticate(Authentication authentication) throws AuthenticationException { + } + + default void postAuthenticate(Authentication authentication) throws AuthenticationException { + } + +} diff --git a/src/main/java/com/inteligr8/activiti/ais/IdentityServiceActivitiAppAuthenticator.java b/src/main/java/com/inteligr8/activiti/ais/IdentityServiceActivitiAppAuthenticator.java new file mode 100644 index 0000000..ccde6b3 --- /dev/null +++ b/src/main/java/com/inteligr8/activiti/ais/IdentityServiceActivitiAppAuthenticator.java @@ -0,0 +1,211 @@ +package com.inteligr8.activiti.ais; + +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.keycloak.representations.AccessToken; +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.context.annotation.Lazy; +import org.springframework.security.core.Authentication; +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; + +/** + * This class/bean implements an Open ID Connect authenticator for Alfresco + * Process Services that supports the creation of missing users and groups and + * synchronizes user/group membership. This is configurable using several + * Spring property values starting with the `keycloak-ext.` prefix. + * + * This implements an internal Authenticator so other authenticators could be + * created in the future. + * + * FIXME This implements is not good for multi-tenancy. + * + * @author brian.long@yudrio.com + */ +@Component("activiti-app.authenticator") +@Lazy +public class IdentityServiceActivitiAppAuthenticator extends AbstractIdentityServiceActivitiAuthenticator implements Authenticator { + + 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]*@.*"); + private final String externalIdmSource = "ais"; + + @Value("${keycloak-ext.createMissingUser:true}") + private boolean createMissingUser; + + @Value("${keycloak-ext.clearNewUserGroups:true}") + private boolean clearNewUserGroups; + + @Value("${keycloak-ext.createMissingGroup:true}") + private boolean createMissingGroup; + + @Value("${keycloak-ext.syncGroupAdd:true}") + private boolean syncGroupAdd; + + @Value("${keycloak-ext.syncGroupRemove:true}") + private boolean syncGroupRemove; + + @Autowired + private LicenseService licenseService; + + @Autowired + private TenantService tenantService; + + @Autowired + private UserService userService; + + @Autowired + private GroupService groupService; + + /** + * This method validates that the user exists, if not, it creates the + * missing user. Without this functionality, SSO straight up fails in APS. + */ + @Override + public void preAuthenticate(Authentication auth) throws AuthenticationException { + Long tenantId = this.findDefaultTenantId(); + this.logger.trace("Tenant ID: {}", tenantId); + + User user = this.findUser(auth, tenantId); + if (user == null) { + if (this.createMissingUser) { + this.logger.debug("User does not yet exist; creating the user: {}", auth.getName()); + + user = this.createUser(auth, tenantId); + this.logger.debug("Created user: {} => {}", user.getId(), user.getExternalId()); + + if (this.clearNewUserGroups) { + this.logger.debug("Clearing groups: {}", user.getId()); + // fetch and remove default groups + user = this.userService.findUserByEmailFetchGroups(user.getEmail()); + for (Group group : user.getGroups()) + this.groupService.deleteUserFromGroup(group, user); + } + } else { + this.logger.info("User does not exist; user creation is disabled: {}", auth.getName()); + } + } + } + + /** + * This method validates that the groups exist, if not, it creates the + * missing ones. Without this functionality, SSO works, but the user's + * authorities are not synchronized. + */ + @Override + public void postAuthenticate(Authentication auth) throws AuthenticationException { + Long tenantId = this.findDefaultTenantId(); + User user = this.findUser(auth, tenantId); + this.logger.debug("Inspecting user: {} => {}", user.getId(), user.getExternalId()); + + this.syncUserAuthorities(user, auth, tenantId); + } + + private Long findDefaultTenantId() { + String defaultTenantName = this.licenseService.getDefaultTenantName(); + this.logger.trace("Default Tenant: {}", defaultTenantName); + + List tenants = this.tenantService.findTenantsByName(defaultTenantName); + if (tenants == null || tenants.isEmpty()) { + this.logger.warn("Default tenant not found"); + return null; + } + + Tenant tenant = tenants.iterator().next(); + return tenant.getId(); + } + + private User findUser(Authentication auth, Long tenantId) { + String email = auth.getName(); + + User user = this.userService.findUserByEmailAndTenantId(email, tenantId); + if (user == null) { + this.logger.debug("User does not exist in tenant; trying tenant-less lookup: {}", email); + user = this.userService.findUserByEmail(email); + } else { + this.logger.trace("Found user: {}", user.getId()); + } + + return user; + } + + private User createUser(Authentication auth, Long tenantId) { + AccessToken atoken = this.getOidcAccessToken(auth); + if (atoken == null) { + this.logger.debug("The OIDC access token could not be found; using email to determine names: {}", auth.getName()); + Matcher emailNamesMatcher = this.emailNamesPattern.matcher(auth.getName()); + if (!emailNamesMatcher.matches()) { + this.logger.warn("The email address could not be parsed for names: {}", auth.getName()); + return this.userService.createNewUserFromExternalStore(auth.getName(), "Unknown", "User", tenantId, auth.getName(), this.externalIdmSource, new Date()); + } else { + String firstName = StringUtils.capitalize(emailNamesMatcher.group(1)); + String lastName = StringUtils.capitalize(emailNamesMatcher.group(2)); + return this.userService.createNewUserFromExternalStore(auth.getName(), firstName, lastName, tenantId, auth.getName(), this.externalIdmSource, new Date()); + } + } else { + return this.userService.createNewUserFromExternalStore(auth.getName(), atoken.getGivenName(), atoken.getFamilyName(), tenantId, auth.getName(), this.externalIdmSource, new Date()); + } + } + + private void syncUserAuthorities(User user, Authentication auth, Long tenantId) { + Set authorities = this.toSet(auth.getAuthorities()); + this.logger.debug("OIDC authorities: {}", authorities); + + // check Activiti groups + User userWithGroups = this.userService.findUserByEmailFetchGroups(user.getEmail()); + for (Group group : userWithGroups.getGroups()) { + this.logger.trace("Inspecting group: {} => ", group.getId(), group.getExternalId()); + + if (authorities.remove(group.getExternalId())) { + // all good + } else { + if (this.syncGroupRemove) { + this.logger.trace("Removing user '{}' from group '{}'", user.getExternalId(), group.getExternalId()); + this.groupService.deleteUserFromGroup(group, userWithGroups); + } else { + this.logger.debug("User/group membership sync disabled; not removing user from group: {} => {}", user.getExternalId(), group.getExternalId()); + } + } + } + + // add remaining authorities into Activiti + for (String authority : authorities) { + this.logger.trace("Syncing group membership: {}", authority); + + Group group = this.groupService.getGroupByExternalId(authority); + if (group == null) { + if (this.createMissingGroup) { + this.logger.trace("Creating new group: {}", authority); + String shortAuthority = authority.replaceFirst("[A-Z]+_", ""); + group = this.groupService.createGroupFromExternalStore(shortAuthority, tenantId, Group.TYPE_SYSTEM_GROUP, null, authority, new Date()); + } else { + this.logger.debug("Group does not exist; group creation is disabled: {}", authority); + } + } + + if (group != null && this.syncGroupAdd) { + this.logger.trace("Adding user '{}' from group '{}'", user.getExternalId(), group.getExternalId()); + this.groupService.addUserToGroup(group, userWithGroups); + } else { + this.logger.debug("User/group membership sync disabled; not adding user to group: {} => {}", user.getExternalId(), group.getExternalId()); + } + } + } +} diff --git a/src/main/java/com/inteligr8/activiti/ais/IdentityServiceActivitiEngineAuthenticator.java b/src/main/java/com/inteligr8/activiti/ais/IdentityServiceActivitiEngineAuthenticator.java new file mode 100644 index 0000000..f9c6f3f --- /dev/null +++ b/src/main/java/com/inteligr8/activiti/ais/IdentityServiceActivitiEngineAuthenticator.java @@ -0,0 +1,159 @@ +package com.inteligr8.activiti.ais; + +import java.util.List; +import java.util.Set; + +import org.activiti.engine.IdentityService; +import org.activiti.engine.identity.Group; +import org.activiti.engine.identity.User; +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.context.annotation.Lazy; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.stereotype.Component; + +/** + * This is an unused implementation for non-APS installation. It is not tested + * and probably pointless. + * + * @author brian.long@yudrio.com + */ +@Component("activiti.authenticator") +@Lazy +public class IdentityServiceActivitiEngineAuthenticator extends AbstractIdentityServiceActivitiAuthenticator implements Authenticator { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Value("${keycloak-ext.createMissingUser:true}") + private boolean createMissingUser; + + @Value("${keycloak-ext.clearNewUserGroups:true}") + private boolean clearNewUserGroups; + + @Value("${keycloak-ext.createMissingGroup:true}") + private boolean createMissingGroup; + + @Value("${keycloak-ext.syncGroupAdd:true}") + private boolean syncGroupAdd; + + @Value("${keycloak-ext.syncGroupRemove:true}") + private boolean syncGroupRemove; + + @Autowired + private IdentityService identityService; + + /** + * This method validates that the user exists, if not, it creates the + * missing user. Without this functionality, SSO straight up fails. + */ + @Override + public void preAuthenticate(Authentication auth) throws AuthenticationException { + User user = this.findUser(auth); + if (user == null) { + if (this.createMissingUser) { + this.logger.debug("User does not yet exist; creating the user: {}", auth.getName()); + + user = this.createUser(auth); + this.logger.debug("Created user: {} => {}", user.getId(), user.getEmail()); + + if (this.clearNewUserGroups) { + this.logger.debug("Clearing groups: {}", user.getId()); + List groups = this.identityService.createGroupQuery() + .groupMember(user.getId()) + .list(); + for (Group group : groups) + this.identityService.deleteMembership(user.getId(), group.getId()); + } + } else { + this.logger.info("User does not exist; user creation is disabled: {}", auth.getName()); + } + } + } + + /** + * This method validates that the groups exist, if not, it creates the + * missing ones. Without this functionality, SSO works, but the user's + * authorities are not synchronized. + */ + @Override + public void postAuthenticate(Authentication auth) throws AuthenticationException { + User user = this.findUser(auth); + this.logger.debug("Inspecting user: {} => {}", user.getId(), user.getEmail()); + + this.syncUserAuthorities(user, auth); + } + + private User findUser(Authentication auth) { + String email = auth.getName(); + + User user = this.identityService.createUserQuery() + .userEmail(email) + .singleResult(); + + return user; + } + + private User createUser(Authentication auth) { + User user = this.identityService.newUser(auth.getName()); + user.setEmail(auth.getName()); + this.identityService.saveUser(user); + return user; + } + + private void syncUserAuthorities(User user, Authentication auth) { + Set authorities = this.toSet(auth.getAuthorities()); + this.logger.debug("OIDC authorities: {}", authorities); + + // check Activiti groups + List groups = this.identityService.createGroupQuery() + .groupMember(user.getEmail()) + .list(); + this.logger.debug("User is currently a member of {} groups", groups.size()); + for (Group group : groups) { + this.logger.trace("Inspecting group: {} => {} ({})", group.getId(), group.getName(), group.getType()); + if (authorities.remove(group.getName())) { + this.logger.trace("Group and membership already exist: {} => {}", user.getEmail(), group.getName()); + // already a member of the group + } else { + if (this.syncGroupRemove) { + this.logger.trace("Group membership not in OIDC token; removing from group: {} => {}", user.getEmail(), group.getName()); + this.identityService.deleteMembership(user.getId(), group.getId()); + } else { + this.logger.debug("User/group membership sync disabled; not removing user from group: {} => {}", user.getId(), group.getId()); + } + } + } + + this.logger.debug("Unaddressed OIDC authorities: {}", authorities); + + // check remainder/unaddressed authorities + for (String authority : authorities) { + this.logger.trace("Inspecting authority: {}", authority); + + Group group = this.identityService.createGroupQuery() + .groupName(authority) + .singleResult(); + if (group == null) { + if (this.createMissingGroup) { + this.logger.trace("Group does not exist; creating one"); + group = this.identityService.newGroup(authority); + group.setName(authority); + this.identityService.saveGroup(group); + } else { + this.logger.info("User does not exist; user creation is disabled: {}", auth.getName()); + } + } + + if (group != null && this.syncGroupAdd) { + this.logger.trace("Group membership not in Activiti; adding to group: {} => {}", user.getEmail(), group.getName()); + this.identityService.createMembership(user.getId(), group.getId()); + } else { + this.logger.debug("User/group membership sync disabled; not adding user to group: {} => {}", user.getId(), group.getId()); + } + } + } + +} diff --git a/src/main/java/com/inteligr8/activiti/ais/IdentityServiceAuthenticationProviderAdapter.java b/src/main/java/com/inteligr8/activiti/ais/IdentityServiceAuthenticationProviderAdapter.java new file mode 100644 index 0000000..ad6c2b9 --- /dev/null +++ b/src/main/java/com/inteligr8/activiti/ais/IdentityServiceAuthenticationProviderAdapter.java @@ -0,0 +1,38 @@ +package com.inteligr8.activiti.ais; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.security.authentication.AuthenticationProvider; + +import com.activiti.api.security.AlfrescoAuthenticationProviderOverride; + +/** + * FIXME This would be nice, but with AIS enabled, it is never called. The use + * of this requires a fix from the Alfresco/Activiti team. Their AIS + * authentication logic appears to have been hastily added, breaking this + * override possibility. We are instead using the heavier weight + * `OidcSecurityConfigurationAdapter` and re-implementing the authentication + * logic discovered in the `activiti-app` project + * `com.activiti.conf.SecurityConfiguration` class. + * + * @author brian.long@yudrio.com + * @see IdentityServiceSecurityConfigurationAdapter + */ +//@Component +public class IdentityServiceAuthenticationProviderAdapter implements AlfrescoAuthenticationProviderOverride { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + @Qualifier("activiti-app.authenticator") + private Authenticator authenticator; + + @Override + public AuthenticationProvider createAuthenticationProvider() { + this.logger.trace("createAuthenticationProvider()"); + return new InterceptingIdentityServiceAuthenticationProvider(this.authenticator); + } + +} diff --git a/src/main/java/com/inteligr8/activiti/ais/IdentityServiceSecurityConfigurationAdapter.java b/src/main/java/com/inteligr8/activiti/ais/IdentityServiceSecurityConfigurationAdapter.java new file mode 100644 index 0000000..eb89481 --- /dev/null +++ b/src/main/java/com/inteligr8/activiti/ais/IdentityServiceSecurityConfigurationAdapter.java @@ -0,0 +1,66 @@ +package com.inteligr8.activiti.ais; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; + +import com.activiti.api.msmt.MsmtTenantResolver; +import com.activiti.api.security.AlfrescoSecurityConfigOverride; +import com.activiti.conf.MsmtProperties; + +/** + * This class/bean overrides the AIS authentication provider, enabling a more + * complete integration with AIS. + * + * FIXME This is not optimal, but with AIS enabled, we cannot use the proper + * override. + * + * @author brian.long@yudrio.com + * @see IdentityServiceAuthenticationProviderAdapter + */ +@Component +public class IdentityServiceSecurityConfigurationAdapter implements AlfrescoSecurityConfigOverride { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Value("${keycloak.ext.odic.enabled:true}") + private boolean enabled; + + @Autowired + protected MsmtProperties msmtProperties; + + @Autowired(required = false) // Only when multi-schema multi-tenant is enabled + protected MsmtTenantResolver tenantResolver; + + @Autowired + @Qualifier("activiti-app.authenticator") + private Authenticator authenticator; + + protected Authenticator getAuthenticator() { + return this.authenticator; + } + + @Override + public void configureGlobal(AuthenticationManagerBuilder authmanBuilder, UserDetailsService userDetailsService) { + this.logger.trace("configureGlobal()"); + + if (this.enabled) { + this.logger.info("Using Keycloak authentication extension, featuring creation of missing users and authority synchronization"); + + InterceptingIdentityServiceAuthenticationProvider provider = new InterceptingIdentityServiceAuthenticationProvider(this.getAuthenticator()); + if (this.msmtProperties.isMultiSchemaMultiTenantEnabled()) + provider.setTenantResolver(this.tenantResolver); + provider.setUserDetailsService(userDetailsService); + provider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); + + authmanBuilder.authenticationProvider(provider); + } + } + +} diff --git a/src/main/java/com/inteligr8/activiti/ais/InterceptingIdentityServiceAuthenticationProvider.java b/src/main/java/com/inteligr8/activiti/ais/InterceptingIdentityServiceAuthenticationProvider.java new file mode 100644 index 0000000..0244023 --- /dev/null +++ b/src/main/java/com/inteligr8/activiti/ais/InterceptingIdentityServiceAuthenticationProvider.java @@ -0,0 +1,47 @@ +package com.inteligr8.activiti.ais; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +import com.activiti.security.identity.service.authentication.provider.IdentityServiceAuthenticationProvider; + +/** + * This class/bean extends the APS AIS OOTB authentication provider. It uses + * an `Authenticator` to pre/post authenticate. The pre-authentication allows + * us to circumvent the problem with AIS and missing users. The + * post-authentication allow us to synchronize groups/authorities. + * + * @author brian.long@yudrio.com + */ +public class InterceptingIdentityServiceAuthenticationProvider extends IdentityServiceAuthenticationProvider { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Authenticator authenticator; + + public InterceptingIdentityServiceAuthenticationProvider(Authenticator authenticator) { + this.authenticator = authenticator; + } + + @Override + public Authentication authenticate(Authentication auth) throws AuthenticationException { + this.logger.trace("authenticate({})", auth.getName()); + + this.authenticator.preAuthenticate(auth); + this.logger.debug("Pre-authenticated user: {}", auth.getName()); + + auth = super.authenticate(auth); + this.logger.debug("Authenticated user '{}' with authorities: {}", auth.getName(), auth.getAuthorities()); + + // FIXME temporary for debugging + if (auth.getName().equals("admin@app.activiti.com")) + return auth; + + this.authenticator.postAuthenticate(auth); + this.logger.debug("Post-authenticated user: {}", auth.getName()); + + return auth; + } + +}