Commit faba551a authored by Brian Long's avatar Brian Long
Browse files

v2.1.x; added APS API support

parent 84d1b4ea
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@

	<groupId>com.inteligr8.activiti</groupId>
	<artifactId>auth-activiti-app-ext</artifactId>
	<version>2.0-SNAPSHOT</version>
	<version>2.1-SNAPSHOT</version>

	<name>Authentication &amp; Authorization for APS</name>
	<description>An Alfresco Process Service App extension providing improved authentication and authorization support.</description>
@@ -45,6 +45,7 @@
		
		<!-- for RAD -->
		<tomcat-rad.version>10-2.2</tomcat-rad.version>
		<aps.hotswap.enabled>false</aps.hotswap.enabled>
		<aps.tomcat.opts.base>-Dspring.main.allow-circular-references=true \
			-Dhibernate.dialect=org.hibernate.dialect.PostgreSQLDialect \
			-Dauth-ext.external.id=keycloak \
+34 −1
Original line number Diff line number Diff line
package com.inteligr8.activiti.auth.oauth;

import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -10,11 +12,16 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.web.SecurityFilterChain;

import com.activiti.security.ProtectedPaths;
import com.activiti.security.identity.service.config.IdentityServiceEnabledCondition;
import com.inteligr8.activiti.auth.service.JwtAuthenticationProvider;
import com.nimbusds.oauth2.sdk.ParseException;

/**
@@ -33,6 +40,9 @@ public class IdentityServiceConfigurationOverride {
    @Autowired
    private ApplicationContext appContext;

    @Autowired
    private JwtAuthenticationProvider jwtAuthenticationProvider;

    @Bean("inteligr8.clientRegistrationRepository")
    @Primary
    public ClientRegistrationRepository clientRegistrationRepository() {
@@ -51,7 +61,7 @@ public class IdentityServiceConfigurationOverride {

    @Bean(OVERRIDE_CLIENT_REGISTRATION_BEANNAME)
    @Primary
    public ClientRegistration clientRegistration1() throws ParseException, InterruptedException {
    public ClientRegistration clientRegistration() throws ParseException, InterruptedException {
        this.logger.trace("clientRegistration()");
        ClientRegistration clientRegistration = this.appContext.getBean(OOTB_CLIENT_REGISTRATION_BEANNAME, ClientRegistration.class);
        
@@ -62,4 +72,27 @@ public class IdentityServiceConfigurationOverride {
            .build();
    }

    /**
     * Slightly lower priority than the one provided OOTB.  This
     * allows for the bean injection of the JwtAuthenticationConverter.
     * 
     * A lower priority means it is applied last.  This means it replaces the
     * JwtAuthenticationConverter provided by Alfresco OOTB.
     * 
     * @see com.activiti.security.identity.service.config.IdentityServiceConfigurationApi#identityServiceApiWebSecurity
     */
    @Bean("inteligr8.identityServiceApiWebSecurity")
    @Order(-5)
    public SecurityFilterChain identityServiceApiWebSecurity(HttpSecurity http) throws Exception {
        http
            .securityMatcher(antMatcher(ProtectedPaths.API_URL_PATH + "/**"))
            .oauth2ResourceServer(oauth2 ->
                oauth2.jwt(jwtConfigurer -> {
                    jwtConfigurer.jwtAuthenticationConverter(this.jwtAuthenticationProvider.create());
                })
            );

        return http.build();
    }

}
+20 −9
Original line number Diff line number Diff line
@@ -30,7 +30,10 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.util.Pair;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;

import com.activiti.domain.idm.Group;
@@ -129,28 +132,36 @@ public class GroupSyncService {
    }
    
    public void sync(OidcUser oidcUser) {
        if (!oidcUser.hasClaim("groups")) {
            this.logger.warn("There is no 'groups' claim to synchronize: {}", oidcUser.getEmail());
            this.logger.debug("The claims available: {}", oidcUser.getClaims().keySet());
        this.sync(oidcUser.getEmail(), oidcUser);
    }
    
    public void sync(Jwt jwt) {
        this.sync(jwt.getClaim(StandardClaimNames.EMAIL), jwt);
    }
    
    public void sync(String email, ClaimAccessor claims) {
        if (!claims.hasClaim("groups")) {
            this.logger.warn("There is no 'groups' claim to synchronize: {}", email);
            this.logger.debug("The claims available: {}", claims.getClaims().keySet());
            return;
        }
        
        Set<String> oidcGroups = new HashSet<>(oidcUser.getClaimAsStringList("groups"));
        this.logger.trace("Incoming OIDC groups: {}: {}", oidcUser.getEmail(), oidcGroups);
        Set<String> oidcGroups = new HashSet<>(claims.getClaimAsStringList("groups"));
        this.logger.trace("Incoming OIDC groups: {}: {}", email, oidcGroups);
        
        oidcGroups = this.filterGroups(oidcGroups);
        Set<String> translatedGroups = this.translateGroups(oidcGroups);
        
        this.logger.debug("Filtered/translated OIDC groups: {}: {}", oidcUser.getEmail(), translatedGroups);
        this.logger.debug("Filtered/translated OIDC groups: {}: {}", email, translatedGroups);
        
        long tenantId = this.tenantFinderService.findTenantId();

        // check Activiti groups
        User user = this.userService.findUserByEmailAndTenantId(oidcUser.getEmail(), tenantId);
        User user = this.userService.findUserByEmailAndTenantId(email, tenantId);
        if (user == null) {
            user = this.userService.findUserByEmail(oidcUser.getEmail());
            user = this.userService.findUserByEmail(email);
            if (user == null)
                throw new UsernameNotFoundException("The user could not be found: " + oidcUser.getEmail());
                throw new UsernameNotFoundException("The user could not be found: " + email);
        }
        User userWithGroups = this.userService.getUser(user.getId(), true);
        this.logger.debug("Discovered user belongs to {} APS groups: {}", userWithGroups.getGroups().size(), user.getExternalId());
+11 −0
Original line number Diff line number Diff line
package com.inteligr8.activiti.auth.service;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.oauth2.jwt.Jwt;

public interface JwtAuthenticationProvider {
    
    Converter<Jwt, AbstractAuthenticationToken> create();

}
+50 −0
Original line number Diff line number Diff line
package com.inteligr8.activiti.auth.service;

import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.jwt.Jwt;

import com.activiti.security.identity.service.config.JwtAuthenticationToken;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

public class SyncingJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
    
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final UserDetailsService userDetailsService;
    private final UserSyncService userSyncService;
    private final GroupSyncService groupSyncService;
    
    public SyncingJwtAuthenticationConverter(UserDetailsService userDetailsService, UserSyncService userSyncService, GroupSyncService groupSyncService) {
        this.userDetailsService = userDetailsService;
        this.userSyncService = userSyncService;
        this.groupSyncService = groupSyncService;
    }
    
    @Override
    public AbstractAuthenticationToken convert(Jwt source) {
        this.logger.trace("convert({}, {})", source.getId(), source.getClaim("email"));
        
        try {
            this.logger.debug("jwt: {}", new ObjectMapper().registerModule(new JavaTimeModule()).writeValueAsString(source));
        } catch (JsonProcessingException jpe) {
            this.logger.error("error", jpe);
        }
        
        this.userSyncService.sync(source);
        this.groupSyncService.sync(source);
        
        UserDetails springUser = this.userDetailsService.loadUserByUsername(source.getClaim("email"));
        return new JwtAuthenticationToken(
                springUser,
                new ArrayList<>(springUser.getAuthorities()));
    }

}
Loading