Merge branch 'develop' into stable
This commit is contained in:
@@ -4,7 +4,7 @@ import org.springframework.context.annotation.ComponentScan;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ComponentScan(basePackages = {"com.inteligr8.activiti.oidc"})
|
@ComponentScan(basePackages = {"com.inteligr8.activiti.ais"})
|
||||||
public class OidcExtSpringComponentScanner {
|
public class KeycloakExtSpringComponentScanner {
|
||||||
|
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package com.inteligr8.activiti.ais;
|
package com.inteligr8.activiti;
|
||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
@@ -2,19 +2,21 @@ package com.inteligr8.activiti.ais;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.keycloak.KeycloakPrincipal;
|
import org.keycloak.KeycloakPrincipal;
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.representations.AccessToken.Access;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
import com.activiti.security.identity.service.authentication.provider.IdentityServiceAuthenticationToken;
|
import com.inteligr8.activiti.Authenticator;
|
||||||
|
|
||||||
public abstract class AbstractIdentityServiceActivitiAuthenticator implements Authenticator {
|
public abstract class AbstractIdentityServiceActivitiAuthenticator implements Authenticator {
|
||||||
|
|
||||||
@@ -22,33 +24,76 @@ public abstract class AbstractIdentityServiceActivitiAuthenticator implements Au
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected AccessToken getOidcAccessToken(Authentication auth) {
|
protected Set<String> getRoles(Authentication auth) {
|
||||||
|
Set<String> authorities = this.toSet(auth.getAuthorities());
|
||||||
|
this.logger.debug("Auto-parsed authorities: {}", authorities);
|
||||||
|
|
||||||
|
if (authorities.isEmpty()) {
|
||||||
|
AccessToken atoken = this.getKeycloakAccessToken(auth);
|
||||||
|
if (atoken == null) {
|
||||||
|
this.logger.debug("Access token not available");
|
||||||
|
return null;
|
||||||
|
} else if (atoken.getRealmAccess() == null && atoken.getResourceAccess().isEmpty()) {
|
||||||
|
this.logger.debug("Access token has no role information");
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
if (atoken.getRealmAccess() != null) {
|
||||||
|
this.logger.debug("Access token realm roles: {}", atoken.getRealmAccess().getRoles());
|
||||||
|
authorities.addAll(atoken.getRealmAccess().getRoles());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Entry<String, Access> resourceAccess : atoken.getResourceAccess().entrySet()) {
|
||||||
|
this.logger.debug("Access token resources '{}' roles: {}", resourceAccess.getKey(), resourceAccess.getValue().getRoles());
|
||||||
|
authorities.addAll(resourceAccess.getValue().getRoles());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug("Access token authorities: {}", authorities);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AccessToken getKeycloakAccessToken(Authentication auth) {
|
||||||
KeycloakSecurityContext ksc = this.getKeycloakSecurityContext(auth);
|
KeycloakSecurityContext ksc = this.getKeycloakSecurityContext(auth);
|
||||||
return ksc.getToken();
|
return ksc == null ? null : ksc.getToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected KeycloakSecurityContext getKeycloakSecurityContext(Authentication auth) {
|
protected KeycloakSecurityContext getKeycloakSecurityContext(Authentication auth) {
|
||||||
if (auth instanceof KeycloakAuthenticationToken) {
|
if (auth.getCredentials() instanceof KeycloakSecurityContext) {
|
||||||
this.logger.debug("Fetching KeycloakSecurityContext from KeycloakAuthenticationToken");
|
this.logger.debug("Found keycloak context in credentials");
|
||||||
if (auth.getPrincipal() instanceof KeycloakPrincipal) {
|
return (KeycloakSecurityContext)auth.getCredentials();
|
||||||
return ((KeycloakPrincipal<? extends KeycloakSecurityContext>)auth.getPrincipal()).getKeycloakSecurityContext();
|
} else if (auth.getPrincipal() instanceof KeycloakPrincipal) {
|
||||||
} else {
|
this.logger.debug("Found keycloak context in principal: {}", auth.getPrincipal());
|
||||||
return null;
|
return ((KeycloakPrincipal<? extends KeycloakSecurityContext>)auth.getPrincipal()).getKeycloakSecurityContext();
|
||||||
}
|
} else if (!(auth instanceof KeycloakAuthenticationToken)) {
|
||||||
} else if (auth instanceof IdentityServiceAuthenticationToken) {
|
this.logger.warn("Unexpected token: {}", auth.getClass());
|
||||||
this.logger.debug("Fetching KeycloakSecurityContext from IdentityServiceAuthenticationToken");
|
|
||||||
OidcKeycloakAccount account = ((IdentityServiceAuthenticationToken)auth).getAccount();
|
|
||||||
return account == null ? null : account.getKeycloakSecurityContext();
|
|
||||||
} else {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KeycloakAuthenticationToken ktoken = (KeycloakAuthenticationToken)auth;
|
||||||
|
if (ktoken.getAccount() != null) {
|
||||||
|
this.logger.debug("Found keycloak context in account: {}", ktoken.getAccount().getPrincipal() == null ? null : ktoken.getAccount().getPrincipal().getName());
|
||||||
|
return ktoken.getAccount().getKeycloakSecurityContext();
|
||||||
|
} else {
|
||||||
|
this.logger.warn("Unable to find keycloak security context");
|
||||||
|
this.logger.debug("Principal: {}", auth.getPrincipal());
|
||||||
|
this.logger.debug("Account: {}", ktoken.getAccount());
|
||||||
|
if (auth.getPrincipal() != null)
|
||||||
|
this.logger.debug("Principal type: {}", auth.getPrincipal().getClass());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Set<String> toSet(Collection<? extends GrantedAuthority> grantedAuthorities) {
|
protected Set<String> toSet(Collection<? extends GrantedAuthority> grantedAuthorities) {
|
||||||
Set<String> authorities = new HashSet<>(grantedAuthorities.size());
|
Set<String> authorities = new HashSet<>(Math.max(grantedAuthorities.size(), 16));
|
||||||
for (GrantedAuthority grantedAuthority : grantedAuthorities)
|
for (GrantedAuthority grantedAuthority : grantedAuthorities) {
|
||||||
authorities.add(grantedAuthority.getAuthority());
|
String authority = StringUtils.trimToNull(grantedAuthority.getAuthority());
|
||||||
|
if (authority == null)
|
||||||
|
this.logger.warn("The granted authorities include an empty authority!?: '{}'", grantedAuthority.getAuthority());
|
||||||
|
authorities.add(authority);
|
||||||
|
}
|
||||||
return authorities;
|
return authorities;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ import com.activiti.service.api.GroupService;
|
|||||||
import com.activiti.service.api.UserService;
|
import com.activiti.service.api.UserService;
|
||||||
import com.activiti.service.idm.TenantService;
|
import com.activiti.service.idm.TenantService;
|
||||||
import com.activiti.service.license.LicenseService;
|
import com.activiti.service.license.LicenseService;
|
||||||
|
import com.inteligr8.activiti.Authenticator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class/bean implements an Open ID Connect authenticator for Alfresco
|
* This class/bean implements an Open ID Connect authenticator for Alfresco
|
||||||
@@ -147,9 +148,9 @@ public class IdentityServiceActivitiAppAuthenticator extends AbstractIdentitySer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private User createUser(Authentication auth, Long tenantId) {
|
private User createUser(Authentication auth, Long tenantId) {
|
||||||
AccessToken atoken = this.getOidcAccessToken(auth);
|
AccessToken atoken = this.getKeycloakAccessToken(auth);
|
||||||
if (atoken == null) {
|
if (atoken == null) {
|
||||||
this.logger.debug("The OIDC access token could not be found; using email to determine names: {}", auth.getName());
|
this.logger.debug("The keycloak access token could not be found; using email to determine names: {}", auth.getName());
|
||||||
Matcher emailNamesMatcher = this.emailNamesPattern.matcher(auth.getName());
|
Matcher emailNamesMatcher = this.emailNamesPattern.matcher(auth.getName());
|
||||||
if (!emailNamesMatcher.matches()) {
|
if (!emailNamesMatcher.matches()) {
|
||||||
this.logger.warn("The email address could not be parsed for names: {}", auth.getName());
|
this.logger.warn("The email address could not be parsed for names: {}", auth.getName());
|
||||||
@@ -165,8 +166,11 @@ public class IdentityServiceActivitiAppAuthenticator extends AbstractIdentitySer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void syncUserAuthorities(User user, Authentication auth, Long tenantId) {
|
private void syncUserAuthorities(User user, Authentication auth, Long tenantId) {
|
||||||
Set<String> authorities = this.toSet(auth.getAuthorities());
|
Set<String> authorities = this.getRoles(auth);
|
||||||
this.logger.debug("OIDC authorities: {}", authorities);
|
if (authorities == null) {
|
||||||
|
this.logger.debug("The user authorities could not be determined; skipping sync: {}", user.getEmail());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// check Activiti groups
|
// check Activiti groups
|
||||||
User userWithGroups = this.userService.findUserByEmailFetchGroups(user.getEmail());
|
User userWithGroups = this.userService.findUserByEmailFetchGroups(user.getEmail());
|
||||||
|
@@ -15,6 +15,8 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.inteligr8.activiti.Authenticator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an unused implementation for non-APS installation. It is not tested
|
* This is an unused implementation for non-APS installation. It is not tested
|
||||||
* and probably pointless.
|
* and probably pointless.
|
||||||
@@ -104,8 +106,11 @@ public class IdentityServiceActivitiEngineAuthenticator extends AbstractIdentity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void syncUserAuthorities(User user, Authentication auth) {
|
private void syncUserAuthorities(User user, Authentication auth) {
|
||||||
Set<String> authorities = this.toSet(auth.getAuthorities());
|
Set<String> authorities = this.getRoles(auth);
|
||||||
this.logger.debug("OIDC authorities: {}", authorities);
|
if (authorities == null) {
|
||||||
|
this.logger.debug("The user authorities could not be determined; skipping sync: {}", user.getEmail());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// check Activiti groups
|
// check Activiti groups
|
||||||
List<Group> groups = this.identityService.createGroupQuery()
|
List<Group> groups = this.identityService.createGroupQuery()
|
||||||
|
@@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
|||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
|
||||||
import com.activiti.api.security.AlfrescoAuthenticationProviderOverride;
|
import com.activiti.api.security.AlfrescoAuthenticationProviderOverride;
|
||||||
|
import com.inteligr8.activiti.Authenticator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIXME This would be nice, but with AIS enabled, it is never called. The use
|
* FIXME This would be nice, but with AIS enabled, it is never called. The use
|
||||||
|
@@ -13,6 +13,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import com.activiti.api.msmt.MsmtTenantResolver;
|
import com.activiti.api.msmt.MsmtTenantResolver;
|
||||||
import com.activiti.api.security.AlfrescoSecurityConfigOverride;
|
import com.activiti.api.security.AlfrescoSecurityConfigOverride;
|
||||||
import com.activiti.conf.MsmtProperties;
|
import com.activiti.conf.MsmtProperties;
|
||||||
|
import com.inteligr8.activiti.Authenticator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class/bean overrides the AIS authentication provider, enabling a more
|
* This class/bean overrides the AIS authentication provider, enabling a more
|
||||||
|
@@ -6,6 +6,7 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
|
||||||
import com.activiti.security.identity.service.authentication.provider.IdentityServiceAuthenticationProvider;
|
import com.activiti.security.identity.service.authentication.provider.IdentityServiceAuthenticationProvider;
|
||||||
|
import com.inteligr8.activiti.Authenticator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class/bean extends the APS AIS OOTB authentication provider. It uses
|
* This class/bean extends the APS AIS OOTB authentication provider. It uses
|
||||||
|
Reference in New Issue
Block a user