Commit 3d3a7433 authored by Brian Long's avatar Brian Long
Browse files

Merge branch 'develop' into stable

parents 9c7641b8 5dfdf845
Loading
Loading
Loading
Loading
+77 −49
Original line number Diff line number Diff line
# Keycloak Extension for Activiti
# Auth Extension for APS (Activiti App)

This library was created to expand the functionality of keycloak integration within the APS (Activiti App) application.  It includes a similar implementation for core Activiti (Activiti Engine), but the core functional is not delivered with that OOTB application at this time.
This library was originally created to expand the functionality of Keycloak integration within the Alfresco Process Services (APS) application.  It has expanded to support general OAuth, closing gaps that remain in the implementation provided by Alfresco.  This is useless for the open source Activiti.

The Activiti App delivers SSO capability and that is about it.  The user must already exist and group synchronization may only happen outside of the context of authentication.  Namely over another protocol (LDAP).
APS delivers SSO capability and that is about it.  It has a few shortcomings:

This module expands SSO to include user creation and group synchronization.  Group synchronization uses the standard access token for Open ID Connect.  These groups are termed "roles".
- The user must already exist in APS, which means they must be sync'd in from LDAP.
- The user roles are for their session only and not synchronized with APS Organizations.  This prevents the user from being included in task candidate group assignments and other group features.

This extension aims to resolve those issues.

## Installation

The installation is simple.  Just include the JAR in the classpath of your Activiti App application.  This is best done by not chaning the `activiti-app.war` file, but instead including it within the classpath using your web container configuration.  For Apache Tomcat, you would add or modify the following context file: `conf/Catalina/localhost/activiti-app.xml`.  Its related contents would be:
The installation is simple.  Just include the JAR in the classpath of your APS application.  This is best done by not chaning the `activiti-app.war` file, but instead including it within the classpath using your web container configuration.  For Apache Tomcat, you would add or modify the following context file: `conf/Catalina/localhost/activiti-app.xml`.  Its related contents would be:

```xml
<Context>
@@ -18,57 +21,82 @@ The installation is simple. Just include the JAR in the classpath of your Activ
</Context>
```

Notice the use of `PostResources` instead of `PreResources`.  This library needs to be loaded after the web application.  This is the best way to load any other extensions or customization to the Activiti App, including `JavaDelegate` implementations.
Notice the use of `PostResources` instead of `PreResources`.  This library needs to be loaded after the web application.  This is the best way to load any other extensions or customization to the Activiti App, including `JavaDelegate` implementations.  If you use the `-security` switch, you will need to give this path permissions in the `catalina.policy` file:

```properties
grant codeBase "file:${catalina.base}/ext/-" {
	permission java.security.AllPermissions
}
```

## Support Matrix

| Keycloak Activiti App Extension | Activiti App    |
| ------------------------------- | --------------- |
| Auth Activiti App Extension | Activiti App    |
| --------------------------- | --------------- |
| v1.0 - v1.2                 | v1.11.x         |
| v1.3                        | v1.11.x - v2.x  |
| v1.4+                           | v24.x+          |
| v2.0+                       | v24.x+          |

## Configuration

The library is highly configurable.  You configure it with properties specified in the `activiti-app.properties` file, which exists somewhere in the root of the classpath.  That is typically in the `lib` folder.  The properties to configure are enumerated in the table below.
The library is highly configurable.  You configure it with properties specified in the `activiti-app.properties` file, which exists somewhere in the root of the classpath.  That is typically in the `lib` folder.  Or you could specify these options with `-D` switches on startup of the web container.  The properties to configure are enumerated in the table below.

This will only work if OAuth is being used.  That would be the case if `activiti.identity-service.enabled` or `security.oauth2.authentication.enabled` is `true`.

### OAuth Authentication/Authorization

The following properties were added to increase the configurability of the built-in OAuth capabilities of APS.  The default in this extension adds the `microprofile-jwt` scope, which is key to providing groups/roles/entitlements.

### Common
| Property                                       | Default   |
| ---------------------------------------------- | --------- |
| `auth-ext.oauth.scopes`                        | `openid`, `profile`, `email`, `microprofile-jwt` |

### OAuth Synchronization

The following properties provide the core functionality of this extension.  That is role synchronization.

| Property                                       | Default   | Description |
| ---------------------------------------------- | --------- | ----------- |
| `keycloak-ext.keycloak.enabled`                | `false`   | Enable Keycloak integration, overriding and extending the OOTB OAuth provider. |
| `keycloak-ext.ootbSecurityConfig.enabled`      | `true`    | Enable OOTB functionality as if this module were not installed.  This adapter operates at priority `0`.  This means it only works if other adapters are disabled (default). |
| `keycloak-ext.default.admins.users`            |           | A default set of administrators to add to the administration role on application startup. |
| `keycloak-ext.clearNewUserDefaultGroups`       | `true`    | When creating a new user, clear any default groups added to that user.  This will not impact existing users. |
| `keycloak-ext.resource.include.regex.patterns` |           | OIDC provides roles in the realm and all permitted clients/resources.  By default all resources are included.  You can limit it with regular expressions with this property. |
| `keycloak-ext.group.format.regex.patterns`     |           | Reformat roles that match the specified regular expressions.  The replacements are specified in another property.  Multiple expressions may be specified by using commas.  Whitespace is not stripped. |
| `keycloak-ext.group.format.regex.replacements` |           | Reformat roles with the specified replacement expressions.  The regular expressions are specified in another property.  Multiple expressions may be specified by using commas.  Whitespace is not stripped. |
| `keycloak-ext.group.include.regex.patterns`    |           | If specified, only the roles that match the specified regular expressions will be considered; otherwise all roles are included. |
| `keycloak-ext.group.exclude.regex.patterns`    |           | If specified, the roles that match the specified regular expressions will be ignored.  This overrides any role explicitly included. |
| `keycloak-ext.syncInternalGroup`               | `false`   | If an internal group with the same name already exists, use that group instead of creating a new one with the same name.  Also register that internal group as external. |

### For Activiti App Only
| `auth-ext.sync.externalId`                     | `oauth`   | This will serve as the external ID for users and as the prefix for the external ID of groups created 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.user.createMissing`             | `true`    | If the user is authenticated, the user may be created in APS. |
| `auth-ext.sync.user.requireGroup`              |           | This is only applicable when `createMissing` is `true`.  If this is unset or the OAuth Authorization Server gives the user the specified group/role, then the user record will be created in APS. |
| `auth-ext.sync.user.clearNewUserGroups`        | `true`    | This is only applicable when `createMissing` is `true`.  All default APS groups will be deleted from the new user record. |
| `auth-ext.sync.group.createMissing`            | `true`    | If a filtered and translated OIDC group has no corresponding APS group, a group will be created in APS.  See `auth-ext.sync.group.capabilities.patterns` for whether that group will be an APS Organization or APS Capability. |
| `auth-ext.sync.group.additions`                | `true`    | If the user isn't in an APS group but OAuth claims the OIDC group, then add them to it. |
| `auth-ext.sync.group.removals`                 | `true`    | If the user is in APS group but OAuth claims no OIDC group, then remove them from it. |
| `auth-ext.sync.group.internal`                 | `false`   | When considering groups for creation or user membership, include internal groups.  Internal groups are ones without an `externalId`. |
| `auth-ext.sync.group.internal.externalize`     | `false`   | This is only applicable when `internal` is `true`.  If an internal group is encountered during the operation of this extension, make it external with the current `externalId`. |
| `auth-ext.sync.group.tenantize`                | `false`   | If a group without a tenant is encountered during the operation of this extension, make it part of the selected tenant. |
| `auth-ext.sync.group.translate.patterns`       |           | A comma delimited set of regular expression patterns for the translation (reformatting) of authorities. |
| `auth-ext.sync.group.translate.replacements`   |           | A comma delimited set of regular expression replacement strings for the translation (reformatting) of authorities. |
| `auth-ext.sync.group.include.patterns`         |           | A comma delimited set of regular expression patterns on what authorities to include.  This is processed before `translate` processing.  A blank value includes everything.  If anything is specified, then only matches could possibly be included; but could still be excluded explicitly. |
| `auth-ext.sync.group.exclude.patterns`         |           | A comma delimited set of regular expression patterns on what authorities to exclude.  This is processed before `translate` processing.  A blank value excludes nothing.  If anything is specified and `include` is empty, then only matches will be excluded.  If both are specified, `exclude` overrules `include` matches. |
| `auth-ext.sync.group.capabilities.patterns`    | `Superusers` | A comma delimited set of regular expression patterns on what authorities to associate with APS Capabilities instead of APS Organizations (default). |


### Authentication Data Fixers

#### Administrator Password Fixer

| Property                                  | Default                  | Description |
| ----------------------------------------- | ------------------------ | ----------- |
| `auth-ext.reset.admin.username`           | `admin@app.activiti.com` |
| `auth-ext.reset.admin.password`           |                          | If set, the user's password will be set to this value on startup; otherwise this fixer is skipped. |

#### Administrator Members Fixer

| Property                                  | Default                  | Description |
| ---------------------------------------------- | ------- | ----------- |
| `keycloak-ext.group.capability.regex.patterns` |         | When creating a new group, sync as an APS Organization, except when the specified pattern matches the role.  In those cases, sync as an APS Capability. |
| `keycloak-ext.external.id`                     | `ais`   | When creating a new group or registering an internal group as external, use this ID as a prefix to the external group ID. |
| ----------------------------------------- | ------------------------ | ----------- |
| `auth-ext.default.admins.users`           |                          | A comma delimited list of user emails; fixer is skipped if empty. |
| `auth-ext.group.admins.name`              | `Superusers`             | The APS Group (Capability or Organization) to add the specified users to. |
| `auth-ext.group.admins.externalId`        |                          | If specified, this APS Group will be considered before the specified `name` field. |

### Rare
#### Administrator Group Fixer

| Property                                  | Default                  | Description |
| ----------------------------------------- | --------------- | ----------- |
| `keycloak-ext.keycloak.priority`          | `-5`            | The order of configurable adapters to use with the application.  Only the lowest priority enabled adapter will be used.  Values of `1`+ will only load if the OOTB adapter is disabled. |
| `keycloak-ext.group.admins.validate`      | `false`         | Whether or not to validate the existence and capabilities of an administrators group on application startup.  This is only applicable for when one is accidently removed and no one has the rights to create one. |
| `keycloak-ext.group.admins.name`          | `admins`        | The name of an administrators group to potentially add and default users on application startup. |
| `keycloak-ext.group.admins.externalId`    | `admins`        | The name of an administrators group to potentially add and default users on application startup. |
| `keycloak-ext.createMissingUser`          | `true`          | Before authentication, check to make sure the user exists as an APS user; if they don't, create the user. |
| `keycloak-ext.createMissingGroup`         | `true`          | Before authorization, check to make sure groups exist for the roles the user claims; if they don't, create the groups. |
| `keycloak-ext.syncGroupAdd`               | `true`          | If the user belongs to a role but not its corresponding group, add the user to the group. |
| `keycloak-ext.syncGroupRemove`            | `true`          | If the user belongs to a group but does not have the corresponding role, remove the user from the group. |

### Deprecated

| Property                                       | Since | Description |
| ---------------------------------------------- | ----- | ----------- |
| `keycloak-ext.ais.*`                           | v24.x | AIS integration was removed.  The `keycloak-ext.keycloak.*` properties must be used instead. |
| ----------------------------------------- | ------------------------ | ----------- |
| `auth-ext.group.admins.validate`          | `false`                  | If `true`, the specified APS Group will be granted all capabilities. |
| `auth-ext.group.admins.name`              | `Superusers`             | The APS Group (Capability or Organization) to add the specified users to. |
| `auth-ext.group.admins.externalId`        |                          | If specified, this APS Group will be considered before the specified `name` field. |
+153 −74
Original line number Diff line number Diff line
@@ -3,11 +3,12 @@
		xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.inteligr8.activiti</groupId>
	<artifactId>keycloak-activiti-app-ext</artifactId>
	<version>1.4.3</version>
	<name>Keycloak Authentication &amp; Authorization for APS</name>
	<description>An Alfresco Process Service App extension providing improved Keycloak/AIS support.</description>
	<url>https://bitbucket.org/inteligr8/keycloak-activiti-app-ext</url>
	<artifactId>auth-activiti-app-ext</artifactId>
	<version>2.0.0</version>

	<name>Authentication &amp; Authorization for APS</name>
	<description>An Alfresco Process Service App extension providing improved authentication and authorization support.</description>
	<url>https://bitbucket.org/inteligr8/auth-activiti-app-ext</url>

	<licenses>
		<license>
@@ -17,9 +18,9 @@
	</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>
		<connection>scm:git:https://bitbucket.org/inteligr8/auth-activiti-app-ext.git</connection>
		<developerConnection>scm:git:git@bitbucket.org:inteligr8/auth-activiti-app-ext.git</developerConnection>
		<url>https://bitbucket.org/inteligr8/auth-activiti-app-ext</url>
	</scm>
	<organization>
		<name>Inteligr8</name>
@@ -39,18 +40,24 @@
		<maven.compiler.target>17</maven.compiler.target>
		<maven.compiler.release>17</maven.compiler.release>

		<aps.version>24.3.0</aps.version>
		<keycloak.version>23.0.7</keycloak.version>
		<spring-security-oauth2.version>6.3.2</spring-security-oauth2.version>
		<aps.version>25.1.1</aps.version>
		
		<!-- for RAD -->
		<tomcat-rad.version>10-2.2</tomcat-rad.version>
		<aps.tomcat.opts.base>-Dspring.main.allow-circular-references=true \
			-Dhibernate.dialect=org.hibernate.dialect.PostgreSQLDialect \
			-Dauth-ext.oauth.enabled=true \
			-Dauth-ext.external.id=keycloak \
			-Dauth-ext.sync.group.translate.patterns=aps-admin \
			-Dauth-ext.sync.group.translate.replacements=Superusers \
			-Dauth-ext.group.admins.validate=true</aps.tomcat.opts.base>
		<aps.timeout>120000</aps.timeout>
		<keycloak.realm>my-app</keycloak.realm>
		<oauth.client.id>aps-app-public</oauth.client.id>
		<oauth.client.secret></oauth.client.secret>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-oauth2-client</artifactId>
			<version>${spring-security-oauth2.version}</version>
			<scope>provided</scope>
		</dependency>
		<!-- Needed for Activiti App Identity Service inheritance/override -->
		<!-- includes activiti-app-logic for API -->
		<dependency>
@@ -60,6 +67,7 @@
			<classifier>classes</classifier>
			<scope>provided</scope>
			<exclusions>
				<!-- not necessary to download for building -->
				<exclusion>
					<groupId>com.activiti</groupId>
					<artifactId>aspose-transformation</artifactId>
@@ -68,41 +76,18 @@
					<groupId>org.alfresco.officeservices</groupId>
					<artifactId>aoservices</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.keycloak</groupId>
			<artifactId>keycloak-spring-security-adapter</artifactId>
			<version>${keycloak.version}</version>
			<exclusions>
				<!-- provided by APS -->
				<exclusion>
					<groupId>org.slf4j</groupId>
					<artifactId>slf4j-api</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.jboss.logging</groupId>
					<artifactId>jboss-logging</artifactId>
				</exclusion>
				<exclusion>
					<groupId>jakarta.activation</groupId>
					<artifactId>*</artifactId>
				</exclusion>
				<!-- very old and overrides real spring version -->
				<exclusion>
					<groupId>org.apache.httpcomponents</groupId>
					<artifactId>*</artifactId>
					<groupId>com.ryantenney.metrics</groupId>
					<artifactId>metrics-spring</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.fasterxml.jackson.core</groupId>
					<artifactId>*</artifactId>
					<groupId>org.springframework.security.oauth</groupId>
					<artifactId>spring-security-oauth2</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.bouncycastle</groupId>
					<artifactId>bcprov-jdk18on</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.bouncycastle</groupId>
					<artifactId>bcpkix-jdk18on</artifactId>
					<groupId>org.springframework.security.oauth.boot</groupId>
					<artifactId>spring-security-oauth2-autoconfigure</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
@@ -111,34 +96,132 @@
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-shade-plugin</artifactId>
				<version>3.6.0</version>
				<groupId>io.repaint.maven</groupId>
				<artifactId>tiles-maven-plugin</artifactId>
				<version>2.40</version>
				<extensions>true</extensions>
				<configuration>
					<tiles>
						<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-aps-ext-rad-tile -->
						<!--
						<tile>com.inteligr8.ootbee:beedk-aps-ext-rad-tile:[1.1.0,2.0.0)</tile>
						-->
						<tile>com.inteligr8.ootbee:beedk-aps-ext-rad-tile:1.1-SNAPSHOT</tile>
					</tiles>
				</configuration>
			</plugin>
		</plugins>
	</build>
	
	<profiles>
		<profile>
			<id>activiti-oauth-confidential</id>
			<activation>
				<property>
					<name>secret</name>
				</property>
			</activation>
			<properties>
				<oauth.client.id>aps-app-confidential</oauth.client.id>
				<oauth.client.secret>a-secret</oauth.client.secret>
			</properties>
		</profile>
		<profile>
			<id>activiti-oauth-legacy</id>
			<activation>
				<property>
					<name>rad</name>
					<value>!spring</value>
				</property>
			</activation>
			<properties>
				<aps.tomcat.opts>${aps.tomcat.opts.base} \
					-Dactiviti.identity-service.enabled=true \
					-Dactiviti.identity-service.realm=${keycloak.realm} \
					-Dactiviti.identity-service.auth-server-url=http://host.docker.internal:${keycloak.server.port} \
					-Dactiviti.identity-service.resource=${oauth.client.id} \
					-Dactiviti.identity-service.credentials.secret=${oauth.client.secret} \
					-Dactiviti.use-browser-based-logout=true \
					-Dalfresco.content.sso.redirect_uri=http://loalhost:8080/activiti-app/app/rest/integration/sso/confirm-auth-request</aps.tomcat.opts>
			</properties>
		</profile>
		<profile>
			<id>activiti-oauth-spring</id>
			<activation>
				<property>
					<name>rad</name>
					<value>spring</value>
				</property>
			</activation>
			<properties>
				<aps.tomcat.opts>${aps.tomcat.opts.base} \
					-Dsecurity.oauth2.authentication.enabled=true \
					-Dsecurity.oauth2.client.registration.my-app.client-id=${oauth.client.id} \
					-Dsecurity.oauth2.client.registration.my-app.client-secret=${oauth.client.secret} \
					-Dsecurity.oauth2.client.registration.my-app.provider=aps-app \
					-Dsecurity.oauth2.client.provider.aps-app.issuer_uri=http://host.docker.internal:${keycloak.server.port}/realms/${keycloak.realm}</aps.tomcat.opts>
			</properties>
		</profile>
		<profile>
			<id>rad-keycloak</id>
			<activation>
				<property>
					<name>rad</name>
				</property>
			</activation>
			<properties>
				<!-- Due to SSL restricitons in previous versions, testing against keyclaok is near impossible. -->
				<!-- This module should still work against nearly all versions of Keycloak that support the OIDC standards -->
				<keycloak.server.version>26.2</keycloak.server.version>
				<keycloak.server.port>8081</keycloak.server.port>
			</properties>
			<build>
				<plugins>
					<plugin>
						<groupId>io.fabric8</groupId>
						<artifactId>docker-maven-plugin</artifactId>
						<version>0.46.0</version>
						<executions>
							<execution>
						<id>shade-jar</id>
						<goals><goal>shade</goal></goals>
								<id>run-keycloak</id>
								<phase>test-compile</phase>
								<goals><goal>start</goal></goals>
								<configuration>
							<shadedArtifactAttached>true</shadedArtifactAttached>
							<relocations>
								<relocation>
									<pattern></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>
									<images>
										<image>
											<name>keycloak/keycloak:${keycloak.server.version}</name>
											<alias>keycloak</alias>
											<run>
												<cmd>start-dev --import-realm</cmd>
												<env>
													<KC_BOOTSTRAP_ADMIN_USERNAME>admin</KC_BOOTSTRAP_ADMIN_USERNAME>
													<KC_BOOTSTRAP_ADMIN_PASSWORD>admin</KC_BOOTSTRAP_ADMIN_PASSWORD>
												</env>
												<ports>
													<port>${keycloak.server.port}:8080</port>
												</ports>
												<network>
													<mode>custom</mode>
													<name>${project.artifactId}</name>
												</network>
												<extraHosts>
													<host>host.docker.internal:host-gateway</host>
												</extraHosts>
												<volumes>
													<bind>
														<volume>${project.basedir}/src/test/resources/keycloak-import:/opt/keycloak/data/import:ro</volume>
													</bind>
												</volumes>
											</run>
										</image>
									</images>
								</configuration>
							</execution>
						</executions>
					</plugin>
				</plugins>
			</build>
	
	<profiles>
		</profile>
		<profile>
			<id>ossrh-release</id>
			<properties>
@@ -202,10 +285,6 @@
	</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>

rad.ps1

0 → 100644
+74 −0

File added.

Preview size limit exceeded, changes collapsed.

rad.sh

0 → 100644
+71 −0

File added.

Preview size limit exceeded, changes collapsed.

+0 −76

File deleted.

Preview size limit exceeded, changes collapsed.

Loading