Commit 6c93637d authored by Brian Long's avatar Brian Long
Browse files

Merge branch 'develop' into stable

parents 48594739 65d80dc8
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ The following properties are used across the functionalities of this extension.
| --------------------------------- | --------- | ----------- |
| `auth-ext.externalId`             | `oauth`   | This will serve as the external ID for users and as the prefix for the external ID of groups created or searched by this extension.  Anything without an external ID is considered internal.  So mismatched external IDs are never considered for anything by this extension. |
| `auth-ext.tenant`                 |           | A preselected tenant for all operations in this extension.  Only required if there are multiple tenants. |
| `auth-ext.sync.resyncInMillis`    | `300000`  | To prevent too many sync checks, how long between seeing the EXACT same token should we wait before doing another sync.  The only time this matters is if the user is manually added to or removed from groups in APS by other means.  Or those groups are deleted. |

### OAuth Authentication/Authorization

+20 −9
Original line number Diff line number Diff line
@@ -4,10 +4,12 @@ import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.core.oidc.StandardClaimNames;
import org.springframework.security.oauth2.jwt.Jwt;

import com.activiti.security.identity.service.config.JwtAuthenticationToken;
@@ -22,6 +24,9 @@ public class SyncingJwtAuthenticationConverter implements Converter<Jwt, Abstrac
    private final UserSyncService userSyncService;
    private final GroupSyncService groupSyncService;
    
    @Autowired
    private TokenRecaller tokenRecaller;
    
    public SyncingJwtAuthenticationConverter(UserDetailsService userDetailsService, UserSyncService userSyncService, GroupSyncService groupSyncService) {
        this.userDetailsService = userDetailsService;
        this.userSyncService = userSyncService;
@@ -30,18 +35,24 @@ public class SyncingJwtAuthenticationConverter implements Converter<Jwt, Abstrac
    
    @Override
    public AbstractAuthenticationToken convert(Jwt source) {
        this.logger.trace("convert({}, {})", source.getId(), source.getClaim("email"));
        
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("convert({}, {})", source.getId(), source.getClaimAsString(StandardClaimNames.EMAIL));
            try {
            this.logger.debug("jwt: {}", new ObjectMapper().registerModule(new JavaTimeModule()).writeValueAsString(source));
                this.logger.trace("jwt: {}", new ObjectMapper().registerModule(new JavaTimeModule()).writeValueAsString(source));
            } catch (JsonProcessingException jpe) {
                this.logger.error("error", jpe);
            }
        }
        
        if (this.tokenRecaller.recall(source.getTokenValue())) {
            this.logger.trace("Skipping sync for '{}' as the same token was already recently sync'd", source.getClaimAsString(StandardClaimNames.EMAIL));
        } else {
            this.userSyncService.sync(source);
            this.groupSyncService.sync(source);
            this.tokenRecaller.add(source.getTokenValue());
        }
        
        UserDetails springUser = this.userDetailsService.loadUserByUsername(source.getClaim("email"));
        UserDetails springUser = this.userDetailsService.loadUserByUsername(source.getClaimAsString(StandardClaimNames.EMAIL));
        return new JwtAuthenticationToken(
                springUser,
                new ArrayList<>(springUser.getAuthorities()));
+12 −5
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ import com.activiti.security.identity.service.config.IdentityServiceKeycloakProp
@Primary
public class SyncingUserService extends OidcUserService {
    
    private final Logger logger = LoggerFactory.getLogger(SyncingUserService.class);
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Autowired
    private UserDetailsService userDetailsService;
@@ -43,6 +43,9 @@ public class SyncingUserService extends OidcUserService {
    @Autowired
    private IdentityServiceKeycloakProperties identityServiceKeycloakProperties;
    
    @Autowired
    private TokenRecaller tokenRecaller;
    
    @Override
    public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
        this.logger.trace("loadUser({}, {})", userRequest.getIdToken().getEmail(), userRequest.getAccessToken().getScopes());
@@ -50,10 +53,14 @@ public class SyncingUserService extends OidcUserService {
        OidcUser oidcUser = super.loadUser(userRequest);
        this.logger.debug("Loaded OIDC user: {}", oidcUser.getEmail());
        
        if (this.tokenRecaller.recall(userRequest.getAccessToken().getTokenValue())) {
            this.logger.trace("Skipping sync for '{}' as the same token was already recently sync'd", userRequest.getIdToken().getEmail());
        } else {
            this.userSyncService.sync(oidcUser);
            this.groupSyncService.sync(oidcUser);
            this.tokenRecaller.add(userRequest.getAccessToken().getTokenValue());
        }
        
        // reload for sync'd group changes
        UserDetails springUser = this.userDetailsService.loadUserByUsername(oidcUser.getEmail());
        
        CustomOAuth2User customOAuth2User = new CustomOAuth2User(
+59 −0
Original line number Diff line number Diff line
package com.inteligr8.activiti.auth.service;

import java.util.concurrent.TimeUnit;

import org.apache.commons.collections4.map.PassiveExpiringMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;

@Component
public class TokenRecaller {
    
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Value("${auth-ext.sync.resyncInMillis:300000}")
    private long resyncTimeInMillis;
    
    private PassiveExpiringMap<String, Long> tokenCache;
    
    @PostConstruct
    private void init() {
        this.tokenCache = new PassiveExpiringMap<>(this.resyncTimeInMillis, TimeUnit.MILLISECONDS);
    }
    
    @Scheduled(timeUnit = TimeUnit.MINUTES, initialDelay = 10L, fixedDelay = 5L)
    private void reap() {
        int tokens = this.tokenCache.size();
        this.logger.trace("Reaping token cache of size: {}", tokens);
        
        synchronized (this.tokenCache) {
            // clear expired keys
            this.tokenCache.entrySet();
        }
        
        this.logger.debug("Reaped {} expired tokens from cache", this.tokenCache.size() - tokens);
    }
    
    public void add(String token) {
        synchronized (this.tokenCache) {
            this.tokenCache.put(token, System.currentTimeMillis() + this.resyncTimeInMillis);
        }
    }
    
    public boolean recall(String token) {
        Long expirationTimeMillis = this.tokenCache.get(token);
        if (expirationTimeMillis == null) {
            return false;
        } else if (expirationTimeMillis < System.currentTimeMillis()) {
            return false;
        } else {
            return true;
        }
    }

}