41 Commits

Author SHA1 Message Date
514ba6bae1 v2.1.2 pom 2025-05-21 15:35:25 -04:00
6c93637dbb Merge branch 'develop' into stable 2025-05-21 15:35:03 -04:00
65d80dc82d throttle sync 2025-05-21 15:34:22 -04:00
4859473938 v2.1.1 pom 2025-05-19 09:44:57 -04:00
99f5a7702c Merge branch 'develop' into stable 2025-05-19 09:44:36 -04:00
9412957e89 Merge branch 'develop' into stable 2025-05-09 19:02:43 -04:00
9c67dad207 v2.0.2 pom 2025-05-05 18:19:10 -04:00
55619d6b4a Merge branch 'develop' into stable 2025-05-05 18:18:53 -04:00
0b032f1f7f Merge branch 'develop' into stable 2025-05-05 11:18:56 -04:00
3d3a7433c5 Merge branch 'develop' into stable 2025-05-04 20:16:17 -04:00
9c7641b858 v1.4.3 pom 2025-04-19 18:19:07 -04:00
0e34f589c3 Merge branch 'develop' into stable 2025-04-19 18:18:49 -04:00
a73543d2a6 v1.4.1 pom 2025-02-12 14:35:41 -05:00
cd472b9269 Merge branch 'develop' into stable 2025-02-12 14:34:10 -05:00
bf848b009c v1.4.0 pom 2024-10-15 15:56:54 -04:00
52b86c0de4 Merge branch 'develop' into stable 2024-10-15 15:56:38 -04:00
8bc0a7e520 Merge branch 'develop' into stable 2024-06-21 13:17:16 -04:00
0601b2b2b2 Merge branch 'develop' into stable 2024-06-21 13:11:04 -04:00
93af3639cc Merge branch 'develop' into stable 2022-07-28 15:25:32 -04:00
0d402f6014 Merge branch 'develop' into stable 2022-07-01 12:15:48 -04:00
e7b6bd644e Merge branch 'develop' into stable 2022-07-01 12:14:32 -04:00
343e1b65b9 added password resetter 2022-01-24 15:29:17 -05:00
14487b62eb v1.2.1 pom 2021-08-31 19:55:19 -04:00
e87a6b68a7 Merge branch 'develop' into stable 2021-08-31 19:54:53 -04:00
5ecb627dbf Merge branch 'develop' into stable 2021-08-27 00:23:17 -04:00
ea487fee31 v1.1.4 pom 2021-08-25 15:54:37 -04:00
9f9ededab2 Merge branch 'develop' into stable 2021-08-25 15:53:50 -04:00
f76105b979 Merge branch 'develop' into stable 2021-08-24 21:22:46 -04:00
a3cb17e402 v1.1.3 pom 2021-08-24 21:15:19 -04:00
c6d0977b2f Merge branch 'develop' into stable 2021-08-24 21:13:44 -04:00
2405a8a313 v1.1.2 pom 2021-08-24 10:00:03 -04:00
173bfed44f Merge branch 'develop' into stable 2021-08-19 18:54:55 -04:00
dc5a7dad39 Merge branch 'develop' into stable 2021-08-19 17:50:01 -04:00
10ed99b0a2 v1.1.1 pom 2021-08-19 17:38:10 -04:00
4e4a6aca8d Merge branch 'develop' into stable 2021-08-19 17:24:24 -04:00
44d0bf533d Merge branch 'develop' into stable 2021-08-18 23:31:20 -04:00
807294881b v1.0.1 pom 2021-08-11 09:17:20 -04:00
a42c754a09 Merge branch 'develop' into stable 2021-08-11 09:08:26 -04:00
8b05c51ef6 Merge branch 'develop' into stable 2021-07-30 15:42:30 -04:00
8bc03e0ea9 Merge branch 'develop' into stable 2021-07-30 15:40:28 -04:00
d32e3c7051 v1.0.0 pom 2021-07-30 15:38:00 -04:00
6 changed files with 271 additions and 15 deletions

View File

@@ -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

178
dependency-reduced-pom.xml Normal file
View File

@@ -0,0 +1,178 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.inteligr8.activiti</groupId>
<artifactId>keycloak-activiti-app-ext</artifactId>
<name>Keycloak Authentication &amp; Authorization for APS</name>
<version>1.4-SNAPSHOT</version>
<description>An Alfresco Process Service App extension providing improved Keycloak/AIS support.</description>
<url>https://bitbucket.org/inteligr8/keycloak-activiti-app-ext</url>
<developers>
<developer>
<id>brian.long</id>
<name>Brian Long</name>
<email>brian@inteligr8.com</email>
<url>https://twitter.com/brianmlong</url>
</developer>
</developers>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007</name>
<url>https://www.gnu.org/licenses/lgpl-3.0.txt</url>
</license>
</licenses>
<scm>
<connection>scm:git:https://bitbucket.org/inteligr8/keycloak-activiti-app-ext.git</connection>
<developerConnection>scm:git:git@bitbucket.org:inteligr8/keycloak-activiti-app-ext.git</developerConnection>
<url>https://bitbucket.org/inteligr8/keycloak-activiti-app-ext</url>
</scm>
<organization>
<name>Inteligr8</name>
<url>https://www.inteligr8.com</url>
</organization>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>shade-jar</id>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<relocations>
<relocation>
<pattern />
<shadedPattern>shaded.keycloak.</shadedPattern>
<excludes>
<exclude>com.activiti.conf.*</exclude>
<exclude>com.activiti.extension.conf.*</exclude>
<exclude>com.inteligr8.activiti.**</exclude>
<exclude>META-INF/**/*</exclude>
</excludes>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>ossrh-release</id>
<build>
<plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>source</id>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>javadoc</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<show>public</show>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-gpg-plugin</artifactId>
<executions>
<execution>
<id>sign</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.7.0</version>
<executions>
<execution>
<id>ossrh-deploy</id>
<phase>deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
</execution>
</executions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
</profile>
</profiles>
<repositories>
<repository>
<id>alfresco-private</id>
<url>https://artifacts.alfresco.com/nexus/content/groups/private</url>
</repository>
<repository>
<id>activiti-releases</id>
<url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-enterprise-releases</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>6.3.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.activiti</groupId>
<artifactId>activiti-app</artifactId>
<version>24.3.0</version>
<classifier>classes</classifier>
<scope>provided</scope>
<exclusions>
<exclusion>
<artifactId>aspose-transformation</artifactId>
<groupId>com.activiti</groupId>
</exclusion>
<exclusion>
<artifactId>aoservices</artifactId>
<groupId>org.alfresco.officeservices</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<maven.compiler.source>17</maven.compiler.source>
<keycloak.version>23.0.7</keycloak.version>
<maven.compiler.target>17</maven.compiler.target>
<slf4j.version>1.7.36</slf4j.version>
<spring-security-oauth2.version>6.3.2</spring-security-oauth2.version>
<aps.version>24.3.0</aps.version>
</properties>
</project>

View File

@@ -5,7 +5,7 @@
<groupId>com.inteligr8.activiti</groupId>
<artifactId>auth-activiti-app-ext</artifactId>
<version>2.1-SNAPSHOT</version>
<version>2.1.2</version>
<name>Authentication &amp; Authorization for APS</name>
<description>An Alfresco Process Service App extension providing improved authentication and authorization support.</description>

View File

@@ -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"));
try {
this.logger.debug("jwt: {}", new ObjectMapper().registerModule(new JavaTimeModule()).writeValueAsString(source));
} catch (JsonProcessingException jpe) {
this.logger.error("error", jpe);
if (this.logger.isTraceEnabled()) {
this.logger.trace("convert({}, {})", source.getId(), source.getClaimAsString(StandardClaimNames.EMAIL));
try {
this.logger.trace("jwt: {}", new ObjectMapper().registerModule(new JavaTimeModule()).writeValueAsString(source));
} catch (JsonProcessingException jpe) {
this.logger.error("error", jpe);
}
}
this.userSyncService.sync(source);
this.groupSyncService.sync(source);
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()));

View File

@@ -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,17 +43,24 @@ 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());
OidcUser oidcUser = super.loadUser(userRequest);
this.logger.debug("Loaded OIDC user: {}", oidcUser.getEmail());
this.userSyncService.sync(oidcUser);
this.groupSyncService.sync(oidcUser);
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(

View File

@@ -0,0 +1,59 @@
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;
}
}
}