Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
880da07a84 | |||
faba551a2d | |||
84d1b4ea2f | |||
187e558177 | |||
c38c1d28df | |||
d631cc5f12 | |||
0a10b06cc8 | |||
76ce7e42d4 | |||
790836194e | |||
dde6dbcdb0 |
67
README.md
67
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Auth Extension for APS (Activiti App)
|
# Auth Extension for APS (Activiti App)
|
||||||
|
|
||||||
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.
|
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 product.
|
||||||
|
|
||||||
APS delivers SSO capability and that is about it. It has a few shortcomings:
|
APS delivers SSO capability and that is about it. It has a few shortcomings:
|
||||||
|
|
||||||
@@ -9,6 +9,8 @@ APS delivers SSO capability and that is about it. It has a few shortcomings:
|
|||||||
|
|
||||||
This extension aims to resolve those issues.
|
This extension aims to resolve those issues.
|
||||||
|
|
||||||
|
The older version of this extension was specific to Keycloak and was named `keycloak-activiti-app-ext`. See the [`develop-v1.4.x` branch](https://git.inteligr8.com/inteligr8/auth-activiti-app-ext/src/branch/develop-v1.4.x/) for details specific to it.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
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:
|
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:
|
||||||
@@ -29,13 +31,17 @@ grant codeBase "file:${catalina.base}/ext/-" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
This extension requires the [`multiext-activiti-app-ext`](https://git.inteligr8.com/inteligr8/multiext-activiti-app-ext). Without it, APS will fail to startup. It is very small and requires no additional configuration.
|
||||||
|
|
||||||
## Support Matrix
|
## Support Matrix
|
||||||
|
|
||||||
| Auth Activiti App Extension | Activiti App |
|
| Activiti App Extension | Activiti App |
|
||||||
| --------------------------- | --------------- |
|
| --------------------------------------- | --------------- |
|
||||||
| v1.0 - v1.2 | v1.11.x |
|
| `keycloak-activiti-app-ext` v1.0 - v1.2 | v1.11.x |
|
||||||
| v1.3 | v1.11.x - v2.x |
|
| `keycloak-activiti-app-ext` v1.3 - v1.4 | v1.11.x - v2.x |
|
||||||
| v2.0+ | v24.x+ |
|
| `auth-activiti-app-ext` v2.0+ | v24.x+ |
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -43,6 +49,13 @@ The library is highly configurable. You configure it with properties specified
|
|||||||
|
|
||||||
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`.
|
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`.
|
||||||
|
|
||||||
|
The following properties are used across the functionalities of this extension.
|
||||||
|
|
||||||
|
| Property | Default | Description |
|
||||||
|
| --------------------------------- | --------- | ----------- |
|
||||||
|
| `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. |
|
||||||
|
|
||||||
### OAuth Authentication/Authorization
|
### 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.
|
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.
|
||||||
@@ -56,11 +69,9 @@ The following properties were added to increase the configurability of the built
|
|||||||
The following properties provide the core functionality of this extension. That is role synchronization.
|
The following properties provide the core functionality of this extension. That is role synchronization.
|
||||||
|
|
||||||
| Property | Default | Description |
|
| Property | Default | Description |
|
||||||
| ---------------------------------------------- | --------- | ----------- |
|
| --------------------------------------------- | ------------ | ----------- |
|
||||||
| `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.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.requireOidcGroup` | | 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.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.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.additions` | `true` | If the user isn't in an APS group but OAuth claims the OIDC group, then add them to it. |
|
||||||
@@ -68,35 +79,37 @@ The following properties provide the core functionality of this extension. That
|
|||||||
| `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` | `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.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.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.include.patterns` | | A comma delimited set of regular expression patterns on what OIDC groups to include. This is processed before `translate` processing. A blank value matches everything. If anything is specified, then only matches could possibly be included. Any matches of the `exclude` property patterns always override though. |
|
||||||
| `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.exclude.patterns` | | A comma delimited set of regular expression patterns on what OIDC groups to exclude. This is processed before `translate` processing. A blank value matches nothing (includes all that pass the `include` constraint). If anything is specified and `include` is empty, then all non-matches are included. If both are specified, `exclude` matches override `include` matches. |
|
||||||
| `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.translate.patterns` | | A comma delimited set of regular expression patterns for the translation (reformatting) of OIDC groups to APS groups. This list corresponds to the `replacements` property and must have the same number of commas. |
|
||||||
| `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.translate.replacements` | | A comma delimited set of regular expression replacement strings for the translation (reformatting) of OIDC groups to APS groups. This list corresponds to the `patterns` property and must have the same number of commas. |
|
||||||
| `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). |
|
| `auth-ext.sync.group.capability.patterns` | `Superusers` | A comma delimited set of regular expression patterns on what translated OIDC groups to associate with APS Capability Groups instead of APS Organization Groups (default). This is processed after `translate` processing. |
|
||||||
|
|
||||||
|
|
||||||
### Authentication Data Fixers
|
### Authentication Data Fixers
|
||||||
|
|
||||||
#### Administrator Password Fixer
|
#### Administrator Password Fixer
|
||||||
|
|
||||||
| Property | Default | Description |
|
This fixer does not execute unless the `auth-ext.reset.admin.password` property is explicitly set. It is meant to be set for a single run to fix scenarios when a non-OAuth administrator cannot access the system. This is useful when your OAuth configuration isn't working and you need to reset your non-OAuth administrator's password.
|
||||||
| ----------------------------------------- | ------------------------ | ----------- |
|
|
||||||
|
| Property | Default |
|
||||||
|
| --------------------------------- | ------------------------ |
|
||||||
| `auth-ext.reset.admin.username` | `admin@app.activiti.com` |
|
| `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. |
|
| `auth-ext.reset.admin.password` | |
|
||||||
|
|
||||||
#### Administrator Members Fixer
|
#### Administrator Members Fixer
|
||||||
|
|
||||||
|
This fixer does not execute unless the `auth-ext.default.admin-users` property is explicitly set. It is meant to be set for a single run to add both non-OAuth and OAuth users to an administrator group. This is useful when OAuth is misconfigured and all administrators lose their administrator rights.
|
||||||
|
|
||||||
| Property | Default | Description |
|
| Property | Default | Description |
|
||||||
| ----------------------------------------- | ------------------------ | ----------- |
|
| ------------------------------------ | ------------ | ----------- |
|
||||||
| `auth-ext.default.admins.users` | | A comma delimited list of user emails; fixer is skipped if empty. |
|
| `auth-ext.default.admins.users` | | A comma delimited list of user emails. |
|
||||||
| `auth-ext.group.admins.name` | `Superusers` | The APS Group (Capability or Organization) to add the specified users to. |
|
| `auth-ext.group.admins.name` | `Superusers` | The APS Group (Capability or Organization) for the specified users to be members. |
|
||||||
| `auth-ext.group.admins.externalId` | | If specified, this APS Group will be considered before the specified `name` field. |
|
|
||||||
|
|
||||||
#### Administrator Group Fixer
|
#### Administrator Group Fixer
|
||||||
|
|
||||||
|
This fixer does not execute unless the `auth-ext.group.admins.validate` property is set to `true`. It is meant to be set for a single run to correct the specified group for administration access. This is useful if you accidentally delete the administrative APS Capability Group, typically called `Administrator` or `Superusers`.
|
||||||
|
|
||||||
| Property | Default | Description |
|
| Property | Default | Description |
|
||||||
| ----------------------------------------- | ------------------------ | ----------- |
|
| ----------------------------------------- | ------------------------ | ----------- |
|
||||||
| `auth-ext.group.admins.validate` | `false` | If `true`, the specified APS Group will be granted all capabilities. |
|
| `auth-ext.group.admins.validate` | `false` |
|
||||||
| `auth-ext.group.admins.name` | `Superusers` | The APS Group (Capability or Organization) to add the specified users to. |
|
| `auth-ext.group.admins.name` | `Superusers` | The APS Capability Group of which to grant all capabilities. |
|
||||||
| `auth-ext.group.admins.externalId` | | If specified, this APS Group will be considered before the specified `name` field. |
|
|
||||||
|
|
||||||
|
@@ -1,178 +0,0 @@
|
|||||||
<?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 & 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>
|
|
19
pom.xml
19
pom.xml
@@ -2,13 +2,14 @@
|
|||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>com.inteligr8.activiti</groupId>
|
<groupId>com.inteligr8.activiti</groupId>
|
||||||
<artifactId>auth-activiti-app-ext</artifactId>
|
<artifactId>auth-activiti-app-ext</artifactId>
|
||||||
<version>2.0.0</version>
|
<version>2.1-SNAPSHOT</version>
|
||||||
|
|
||||||
<name>Authentication & Authorization for APS</name>
|
<name>Authentication & Authorization for APS</name>
|
||||||
<description>An Alfresco Process Service App extension providing improved authentication and authorization support.</description>
|
<description>An Alfresco Process Service App extension providing improved authentication and authorization support.</description>
|
||||||
<url>https://bitbucket.org/inteligr8/auth-activiti-app-ext</url>
|
<url>https://git.inteligr8.com/inteligr8/auth-activiti-app-ext</url>
|
||||||
|
|
||||||
<licenses>
|
<licenses>
|
||||||
<license>
|
<license>
|
||||||
@@ -18,9 +19,9 @@
|
|||||||
</licenses>
|
</licenses>
|
||||||
|
|
||||||
<scm>
|
<scm>
|
||||||
<connection>scm:git:https://bitbucket.org/inteligr8/auth-activiti-app-ext.git</connection>
|
<connection>scm:git:https://git.inteligr8.com/inteligr8/auth-activiti-app-ext.git</connection>
|
||||||
<developerConnection>scm:git:git@bitbucket.org:inteligr8/auth-activiti-app-ext.git</developerConnection>
|
<developerConnection>scm:git:git@git.inteligr8.com:inteligr8/auth-activiti-app-ext.git</developerConnection>
|
||||||
<url>https://bitbucket.org/inteligr8/auth-activiti-app-ext</url>
|
<url>https://git.inteligr8.com/inteligr8/auth-activiti-app-ext</url>
|
||||||
</scm>
|
</scm>
|
||||||
<organization>
|
<organization>
|
||||||
<name>Inteligr8</name>
|
<name>Inteligr8</name>
|
||||||
@@ -44,9 +45,9 @@
|
|||||||
|
|
||||||
<!-- for RAD -->
|
<!-- for RAD -->
|
||||||
<tomcat-rad.version>10-2.2</tomcat-rad.version>
|
<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 \
|
<aps.tomcat.opts.base>-Dspring.main.allow-circular-references=true \
|
||||||
-Dhibernate.dialect=org.hibernate.dialect.PostgreSQLDialect \
|
-Dhibernate.dialect=org.hibernate.dialect.PostgreSQLDialect \
|
||||||
-Dauth-ext.oauth.enabled=true \
|
|
||||||
-Dauth-ext.external.id=keycloak \
|
-Dauth-ext.external.id=keycloak \
|
||||||
-Dauth-ext.sync.group.translate.patterns=aps-admin \
|
-Dauth-ext.sync.group.translate.patterns=aps-admin \
|
||||||
-Dauth-ext.sync.group.translate.replacements=Superusers \
|
-Dauth-ext.sync.group.translate.replacements=Superusers \
|
||||||
@@ -91,6 +92,12 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.inteligr8.activiti</groupId>
|
||||||
|
<artifactId>multiext-activiti-app-ext</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@@ -64,7 +64,7 @@ public class ActivitiAppAdministratorGroupFixer implements DataFixer {
|
|||||||
@Value("${auth-ext.group.admins.name:Superusers}")
|
@Value("${auth-ext.group.admins.name:Superusers}")
|
||||||
private String adminGroupName;
|
private String adminGroupName;
|
||||||
|
|
||||||
@Value("${auth-ext.sync.externalId:oauth}")
|
@Value("${auth-ext.externalId:oauth}")
|
||||||
protected String externalIdmSource;
|
protected String externalIdmSource;
|
||||||
|
|
||||||
@Value("${auth-ext.group.admins.validate:false}")
|
@Value("${auth-ext.group.admins.validate:false}")
|
||||||
|
@@ -58,7 +58,7 @@ public class ActivitiAppAdministratorMembersFixer implements DataFixer {
|
|||||||
@Value("${auth-ext.group.admins.name:Superusers}")
|
@Value("${auth-ext.group.admins.name:Superusers}")
|
||||||
private String adminGroupName;
|
private String adminGroupName;
|
||||||
|
|
||||||
@Value("${auth-ext.group.admins.externalId:#{null}}")
|
@Value("${auth-ext.externalId:oauth}")
|
||||||
private String adminGroupExternalId;
|
private String adminGroupExternalId;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -1,32 +0,0 @@
|
|||||||
package com.inteligr8.activiti.auth;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Primary;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import com.activiti.api.boot.BootstrapConfigurer;
|
|
||||||
|
|
||||||
@Component("bootstrap.proxy")
|
|
||||||
@Primary
|
|
||||||
public class Bootstrapper implements BootstrapConfigurer {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void applicationContextInitialized(ApplicationContext applicationContext) {
|
|
||||||
Map<String, BootstrapConfigurer> bootstraps = applicationContext.getBeansOfType(BootstrapConfigurer.class);
|
|
||||||
bootstraps.remove("bootstrap.proxy");
|
|
||||||
|
|
||||||
this.logger.debug("Executing {} bootstrap configurers", bootstraps.size());
|
|
||||||
for (Entry<String, BootstrapConfigurer> bootstrap : bootstraps.entrySet()) {
|
|
||||||
this.logger.trace("Executing bootstrap configurer: {}: {}", bootstrap.getKey(), bootstrap.getValue().getClass());
|
|
||||||
bootstrap.getValue().applicationContextInitialized(applicationContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,5 +1,8 @@
|
|||||||
package com.inteligr8.activiti.auth.oauth;
|
package com.inteligr8.activiti.auth.oauth;
|
||||||
|
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -10,11 +13,22 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Conditional;
|
import org.springframework.context.annotation.Conditional;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Primary;
|
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.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
||||||
|
|
||||||
|
import com.activiti.domain.idm.Capabilities;
|
||||||
|
import com.activiti.security.ActivitiAppRequestHeaderService;
|
||||||
|
import com.activiti.security.ActivitiRestAuthorizationService;
|
||||||
|
import com.activiti.security.ProtectedPaths;
|
||||||
import com.activiti.security.identity.service.config.IdentityServiceEnabledCondition;
|
import com.activiti.security.identity.service.config.IdentityServiceEnabledCondition;
|
||||||
|
import com.inteligr8.activiti.auth.service.JwtAuthenticationProvider;
|
||||||
import com.nimbusds.oauth2.sdk.ParseException;
|
import com.nimbusds.oauth2.sdk.ParseException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,6 +47,15 @@ public class IdentityServiceConfigurationOverride {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ApplicationContext appContext;
|
private ApplicationContext appContext;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtAuthenticationProvider jwtAuthenticationProvider;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ActivitiAppRequestHeaderService appRequestHeaderService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ActivitiRestAuthorizationService restAuthorizationService;
|
||||||
|
|
||||||
@Bean("inteligr8.clientRegistrationRepository")
|
@Bean("inteligr8.clientRegistrationRepository")
|
||||||
@Primary
|
@Primary
|
||||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||||
@@ -51,7 +74,7 @@ public class IdentityServiceConfigurationOverride {
|
|||||||
|
|
||||||
@Bean(OVERRIDE_CLIENT_REGISTRATION_BEANNAME)
|
@Bean(OVERRIDE_CLIENT_REGISTRATION_BEANNAME)
|
||||||
@Primary
|
@Primary
|
||||||
public ClientRegistration clientRegistration1() throws ParseException, InterruptedException {
|
public ClientRegistration clientRegistration() throws ParseException, InterruptedException {
|
||||||
this.logger.trace("clientRegistration()");
|
this.logger.trace("clientRegistration()");
|
||||||
ClientRegistration clientRegistration = this.appContext.getBean(OOTB_CLIENT_REGISTRATION_BEANNAME, ClientRegistration.class);
|
ClientRegistration clientRegistration = this.appContext.getBean(OOTB_CLIENT_REGISTRATION_BEANNAME, ClientRegistration.class);
|
||||||
|
|
||||||
@@ -62,4 +85,62 @@ public class IdentityServiceConfigurationOverride {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slightly higher priority than the one provided OOTB. This allows for
|
||||||
|
* the bean injection of the `JwtAuthenticationConverter`.
|
||||||
|
*
|
||||||
|
* This is basically a copy of what is provided OOTB, but:
|
||||||
|
*
|
||||||
|
* - The ability to configure the `JwtAuthenticationConverter`.
|
||||||
|
* - Allow non-UI access to `/app/rest/*`
|
||||||
|
*
|
||||||
|
* @see com.activiti.security.identity.service.config.IdentityServiceConfigurationApi#identityServiceApiWebSecurity
|
||||||
|
*/
|
||||||
|
@Bean("inteligr8.identityServiceApiWebSecurity")
|
||||||
|
@Order(-5)
|
||||||
|
public SecurityFilterChain identityServiceApiWebSecurity(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.securityMatchers(matchers -> {
|
||||||
|
matchers.requestMatchers(
|
||||||
|
// same as OOTB
|
||||||
|
antMatcher(ProtectedPaths.API_URL_PATH + "/**"),
|
||||||
|
|
||||||
|
// want to also allow non-UI access to the the protected API
|
||||||
|
// we do this for anything with an `Authorization` header, as the UI uses session-based authorization
|
||||||
|
new AndRequestMatcher(new RequestHeaderRequestMatcher("Authorization"), antMatcher(ProtectedPaths.APP_URL_PATH + "/rest/**"))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.csrf(csrf -> {
|
||||||
|
csrf.disable();
|
||||||
|
})
|
||||||
|
.cors(withDefaults())
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // Stores no Session for API calls
|
||||||
|
.oauth2ResourceServer(oauth2 ->
|
||||||
|
oauth2.jwt(jwtConfigurer -> {
|
||||||
|
// here is where we are injecting a Spring extendible `JwtAuthenticationConverter`.
|
||||||
|
jwtConfigurer.jwtAuthenticationConverter(this.jwtAuthenticationProvider.create());
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.authorizeHttpRequests(request ->
|
||||||
|
request
|
||||||
|
// same as OOTB
|
||||||
|
.requestMatchers(antMatcher(ProtectedPaths.API_URL_PATH + "/enterprise/**"))
|
||||||
|
.access(this.appRequestHeaderService)
|
||||||
|
.requestMatchers(antMatcher(ProtectedPaths.API_URL_PATH + "/**"))
|
||||||
|
.access(this.restAuthorizationService)
|
||||||
|
|
||||||
|
// borrowed from OOTB /app/rest security
|
||||||
|
.requestMatchers(antMatcher(ProtectedPaths.APP_URL_PATH + "/rest/reporting/**"))
|
||||||
|
.hasAuthority(Capabilities.ACCESS_REPORTS)
|
||||||
|
|
||||||
|
.requestMatchers(
|
||||||
|
antMatcher(ProtectedPaths.API_URL_PATH + "/**"),
|
||||||
|
antMatcher(ProtectedPaths.APP_URL_PATH + "/rest/**")
|
||||||
|
)
|
||||||
|
.authenticated()
|
||||||
|
);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,10 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.data.util.Pair;
|
import org.springframework.data.util.Pair;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
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.core.oidc.user.OidcUser;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.activiti.domain.idm.Group;
|
import com.activiti.domain.idm.Group;
|
||||||
@@ -56,7 +59,7 @@ public class GroupSyncService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private TenantFinderService tenantFinderService;
|
private TenantFinderService tenantFinderService;
|
||||||
|
|
||||||
@Value("${auth-ext.sync.externalId:oauth}")
|
@Value("${auth-ext.externalId:oauth}")
|
||||||
protected String externalIdmSource;
|
protected String externalIdmSource;
|
||||||
|
|
||||||
@Value("${auth-ext.sync.group.createMissing:true}")
|
@Value("${auth-ext.sync.group.createMissing:true}")
|
||||||
@@ -129,28 +132,36 @@ public class GroupSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void sync(OidcUser oidcUser) {
|
public void sync(OidcUser oidcUser) {
|
||||||
if (!oidcUser.hasClaim("groups")) {
|
this.sync(oidcUser.getEmail(), oidcUser);
|
||||||
this.logger.warn("There is no 'groups' claim to synchronize: {}", oidcUser.getEmail());
|
}
|
||||||
this.logger.debug("The claims available: {}", oidcUser.getClaims().keySet());
|
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> oidcGroups = new HashSet<>(oidcUser.getClaimAsStringList("groups"));
|
Set<String> oidcGroups = new HashSet<>(claims.getClaimAsStringList("groups"));
|
||||||
this.logger.trace("Incoming OIDC groups: {}: {}", oidcUser.getEmail(), oidcGroups);
|
this.logger.trace("Incoming OIDC groups: {}: {}", email, oidcGroups);
|
||||||
|
|
||||||
oidcGroups = this.filterGroups(oidcGroups);
|
oidcGroups = this.filterGroups(oidcGroups);
|
||||||
oidcGroups = this.translateGroups(oidcGroups);
|
Set<String> translatedGroups = this.translateGroups(oidcGroups);
|
||||||
|
|
||||||
this.logger.debug("Filtered/translated OIDC groups: {}: {}", oidcUser.getEmail(), oidcGroups);
|
this.logger.debug("Filtered/translated OIDC groups: {}: {}", email, translatedGroups);
|
||||||
|
|
||||||
long tenantId = this.tenantFinderService.findTenantId();
|
long tenantId = this.tenantFinderService.findTenantId();
|
||||||
|
|
||||||
// check Activiti groups
|
// check Activiti groups
|
||||||
User user = this.userService.findUserByEmailAndTenantId(oidcUser.getEmail(), tenantId);
|
User user = this.userService.findUserByEmailAndTenantId(email, tenantId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
user = this.userService.findUserByEmail(oidcUser.getEmail());
|
user = this.userService.findUserByEmail(email);
|
||||||
if (user == null)
|
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);
|
User userWithGroups = this.userService.getUser(user.getId(), true);
|
||||||
this.logger.debug("Discovered user belongs to {} APS groups: {}", userWithGroups.getGroups().size(), user.getExternalId());
|
this.logger.debug("Discovered user belongs to {} APS groups: {}", userWithGroups.getGroups().size(), user.getExternalId());
|
||||||
@@ -166,7 +177,7 @@ public class GroupSyncService {
|
|||||||
this.logger.trace("Inspecting APS group: {} => {} ({})", group.getId(), group.getName(), group.getExternalId());
|
this.logger.trace("Inspecting APS group: {} => {} ({})", group.getId(), group.getName(), group.getExternalId());
|
||||||
|
|
||||||
if (group.getExternalId() != null) {
|
if (group.getExternalId() != null) {
|
||||||
String oidcGroup = this.apsGroupExternalIdToOidcGroup(group.getExternalId());
|
String translatedGroup = this.apsGroupExternalIdToTranslatedOidcGroup(group.getExternalId());
|
||||||
|
|
||||||
if (this.retenantUntenantedGroups && group.getTenantId() == null) {
|
if (this.retenantUntenantedGroups && group.getTenantId() == null) {
|
||||||
this.logger.warn("Moving tenant-less APS group to tenant: {} => {}", group.getName(), tenantId);
|
this.logger.warn("Moving tenant-less APS group to tenant: {} => {}", group.getName(), tenantId);
|
||||||
@@ -175,24 +186,25 @@ public class GroupSyncService {
|
|||||||
this.groupService.save(group);
|
this.groupService.save(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oidcGroups.remove(oidcGroup)) {
|
if (translatedGroups.remove(translatedGroup)) {
|
||||||
this.logger.trace("User already belongs to APS group mapped to by OIDC group: {}: {} => {}", user.getExternalId(), oidcGroup, group.getName());
|
this.logger.trace("User already belongs to APS group mapped to by (translated) OIDC group: {}: {} => {}", user.getExternalId(), translatedGroup, group.getName());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String oidcGroup = this.apsGroupNameToOidcGroup(group.getName());
|
String translatedGroup = this.apsGroupNameToTranslatedOidcGroup(group.getName());
|
||||||
|
|
||||||
|
if (translatedGroups.remove(translatedGroup)) {
|
||||||
|
this.logger.trace("User already belongs to APS group mapped to by (translated) OIDC group: {}: {} => {}", user.getExternalId(), translatedGroup, group.getName());
|
||||||
|
|
||||||
if (this.externalizeMatchingInternalGroups) {
|
if (this.externalizeMatchingInternalGroups) {
|
||||||
this.logger.warn("Classifying internal APS group as external: {} => {}", group.getName(), this.externalIdmSource);
|
this.logger.warn("Classifying internal APS group as external: {} => {}", group.getName(), this.externalIdmSource);
|
||||||
// register the group as external
|
// register the group as external
|
||||||
group.setExternalId(this.oidcGroupToApsGroupExternalId(oidcGroup));
|
group.setExternalId(this.translatedOidcGroupToApsGroupExternalId(translatedGroup));
|
||||||
group.setLastUpdate(new Date());
|
group.setLastUpdate(new Date());
|
||||||
this.groupService.save(group);
|
this.groupService.save(group);
|
||||||
// internal role already existed and the user is already a member
|
// internal role already existed and the user is already a member
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oidcGroups.remove(oidcGroup)) {
|
|
||||||
this.logger.trace("User already belongs to APS group mapped to by OIDC group: {}: {} => {}", user.getExternalId(), oidcGroup, group.getName());
|
|
||||||
continue;
|
continue;
|
||||||
} else if (!this.syncInternalGroups) {
|
} else if (!this.syncInternalGroups) {
|
||||||
this.logger.trace("Internal APS group membership sync disabled; not considering removal of user from APS group: {} => {}", user.getExternalId(), group.getName());
|
this.logger.trace("Internal APS group membership sync disabled; not considering removal of user from APS group: {} => {}", user.getExternalId(), group.getName());
|
||||||
@@ -211,21 +223,21 @@ public class GroupSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the user needs to be added to the remaining authorities
|
// the user needs to be added to the remaining authorities
|
||||||
for (String oidcGroup : oidcGroups) {
|
for (String translatedGroup : translatedGroups) {
|
||||||
this.logger.trace("Inspecting unaccounted for OIDC group: {}", oidcGroup);
|
this.logger.trace("Inspecting unaccounted for (translated) OIDC group: {}", translatedGroup);
|
||||||
|
|
||||||
Group group;
|
Group group;
|
||||||
try {
|
try {
|
||||||
group = this.groupService.getGroupByExternalIdAndTenantId(this.oidcGroupToApsGroupExternalId(oidcGroup), tenantId);
|
group = this.groupService.getGroupByExternalIdAndTenantId(this.translatedOidcGroupToApsGroupExternalId(translatedGroup), tenantId);
|
||||||
} catch (NonUniqueResultException nure) {
|
} catch (NonUniqueResultException nure) {
|
||||||
this.logger.warn("There are multiple groups matching the OIDC group for the external system: {} [{}]; skipping consideration of OIDC group", oidcGroup, this.externalIdmSource);
|
this.logger.warn("There are multiple groups matching the (translated) OIDC group for the external system: {} [{}]; skipping consideration of OIDC group", translatedGroup, this.externalIdmSource);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (group == null && this.syncInternalGroups) {
|
if (group == null && this.syncInternalGroups) {
|
||||||
List<Group> groups = this.groupService.getGroupByNameAndTenantId(this.oidcGroupToApsGroupName(oidcGroup), tenantId);
|
List<Group> groups = this.groupService.getGroupByNameAndTenantId(this.translatedOidcGroupToApsGroupName(translatedGroup), tenantId);
|
||||||
if (groups.size() > 1) {
|
if (groups.size() > 1) {
|
||||||
this.logger.warn("There are multiple APS groups matching the OIDC group: {} [{}]; skipping consideration of OIDC group", oidcGroup, this.externalIdmSource);
|
this.logger.warn("There are multiple APS groups matching the (translated) OIDC group: {} [{}]; skipping consideration of OIDC group", translatedGroup, this.externalIdmSource);
|
||||||
continue;
|
continue;
|
||||||
} else if (groups.size() == 1) {
|
} else if (groups.size() == 1) {
|
||||||
group = groups.iterator().next();
|
group = groups.iterator().next();
|
||||||
@@ -233,7 +245,7 @@ public class GroupSyncService {
|
|||||||
if (this.externalizeMatchingInternalGroups) {
|
if (this.externalizeMatchingInternalGroups) {
|
||||||
this.logger.debug("Found an internal APS group; registering as external: {}", group.getName());
|
this.logger.debug("Found an internal APS group; registering as external: {}", group.getName());
|
||||||
|
|
||||||
group.setExternalId(this.oidcGroupToApsGroupExternalId(oidcGroup));
|
group.setExternalId(this.translatedOidcGroupToApsGroupExternalId(translatedGroup));
|
||||||
group.setLastSyncTimeStamp(new Date());
|
group.setLastSyncTimeStamp(new Date());
|
||||||
group.setLastUpdate(new Date());
|
group.setLastUpdate(new Date());
|
||||||
this.groupService.save(group);
|
this.groupService.save(group);
|
||||||
@@ -243,11 +255,11 @@ public class GroupSyncService {
|
|||||||
|
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
if (!this.createMissing) {
|
if (!this.createMissing) {
|
||||||
this.logger.debug("APS Group does not exist for OIDC group; APS group creation is disabled; OIDC group will go unrecognized: {}", oidcGroup);
|
this.logger.debug("APS Group does not exist for (translated) OIDC group; APS group creation is disabled; OIDC group will go unrecognized: {}", translatedGroup);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
group = this.createApsGroup(oidcGroup, tenantId);
|
group = this.createApsGroup(translatedGroup, tenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.syncAdditions) {
|
if (this.syncAdditions) {
|
||||||
@@ -260,13 +272,13 @@ public class GroupSyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Group createApsGroup(String oidcGroup, long tenantId) {
|
protected Group createApsGroup(String translatedGroup, long tenantId) {
|
||||||
this.logger.debug("APS Group does not exist for OIDC group; will attempt to create: {}", oidcGroup);
|
this.logger.debug("APS Group does not exist for (translated) OIDC group; will attempt to create: {}", translatedGroup);
|
||||||
String name = this.oidcGroupToApsGroupName(oidcGroup);
|
String name = this.translatedOidcGroupToApsGroupName(translatedGroup);
|
||||||
String externalId = this.oidcGroupToApsGroupExternalId(oidcGroup);
|
String externalId = this.translatedOidcGroupToApsGroupExternalId(translatedGroup);
|
||||||
|
|
||||||
boolean syncAsOrg = this.isOidcGroupToBeOrganization(oidcGroup);
|
boolean syncAsOrg = this.isTranslatedOidcGroupToBeOrganization(translatedGroup);
|
||||||
this.logger.trace("Creating new APS group as {}: {}", syncAsOrg ? "organization" : "capability", oidcGroup);
|
this.logger.trace("Creating new APS group as {}: {}", syncAsOrg ? "organization" : "capability", translatedGroup);
|
||||||
int type = syncAsOrg ? Group.TYPE_FUNCTIONAL_GROUP : Group.TYPE_SYSTEM_GROUP;
|
int type = syncAsOrg ? Group.TYPE_FUNCTIONAL_GROUP : Group.TYPE_SYSTEM_GROUP;
|
||||||
|
|
||||||
Group apsGroup = this.groupService.createGroupFromExternalStore(name, tenantId, type, null, externalId, new Date());
|
Group apsGroup = this.groupService.createGroupFromExternalStore(name, tenantId, type, null, externalId, new Date());
|
||||||
@@ -330,29 +342,29 @@ public class GroupSyncService {
|
|||||||
return translatedGroups;
|
return translatedGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String oidcGroupToApsGroupExternalId(String group) {
|
private String translatedOidcGroupToApsGroupExternalId(String group) {
|
||||||
return this.externalIdmSource + "_" + group;
|
return this.externalIdmSource + "_" + group;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String apsGroupExternalIdToOidcGroup(String externalId) {
|
private String apsGroupExternalIdToTranslatedOidcGroup(String externalId) {
|
||||||
int underscorePos = externalId.indexOf('_');
|
int underscorePos = externalId.indexOf('_');
|
||||||
return underscorePos < 0 ? externalId : externalId.substring(underscorePos + 1);
|
return underscorePos < 0 ? externalId : externalId.substring(underscorePos + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String oidcGroupToApsGroupName(String group) {
|
private String translatedOidcGroupToApsGroupName(String group) {
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String apsGroupNameToOidcGroup(String externalId) {
|
private String apsGroupNameToTranslatedOidcGroup(String externalId) {
|
||||||
return externalId;
|
return externalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isOidcGroupToBeOrganization(String role) {
|
private boolean isTranslatedOidcGroupToBeOrganization(String translatedGroup) {
|
||||||
if (this.capabilities.isEmpty())
|
if (this.capabilities.isEmpty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
for (Pattern regex : this.capabilities) {
|
for (Pattern regex : this.capabilities) {
|
||||||
Matcher matcher = regex.matcher(role);
|
Matcher matcher = regex.matcher(translatedGroup);
|
||||||
if (matcher.matches())
|
if (matcher.matches())
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
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();
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,50 @@
|
|||||||
|
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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,27 @@
|
|||||||
|
package com.inteligr8.activiti.auth.service;
|
||||||
|
|
||||||
|
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.UserDetailsService;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SyncingJwtAuthenticationProvider implements JwtAuthenticationProvider {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserSyncService userSyncService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private GroupSyncService groupSyncService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Converter<Jwt, AbstractAuthenticationToken> create() {
|
||||||
|
return new SyncingJwtAuthenticationConverter(this.userDetailsService, this.userSyncService, this.groupSyncService);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -20,12 +20,16 @@ import com.activiti.security.identity.service.config.IdentityServiceKeycloakProp
|
|||||||
* Activiti Identity Service configuration is enabled. When it isn't
|
* Activiti Identity Service configuration is enabled. When it isn't
|
||||||
* enabled, it will still serve as the default OIDC user service for
|
* enabled, it will still serve as the default OIDC user service for
|
||||||
* Spring Security.
|
* Spring Security.
|
||||||
|
*
|
||||||
|
* This is only executed with non-API authentication and authorization use
|
||||||
|
* cases. API authentication/authorization uses the
|
||||||
|
* `SyncingJwtAuthenitcationConverter`.
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@Primary
|
@Primary
|
||||||
public class OIDCUserService extends OidcUserService {
|
public class SyncingUserService extends OidcUserService {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(OIDCUserService.class);
|
private final Logger logger = LoggerFactory.getLogger(SyncingUserService.class);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserDetailsService userDetailsService;
|
private UserDetailsService userDetailsService;
|
@@ -9,7 +9,10 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
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.core.oidc.user.OidcUser;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.activiti.domain.idm.Group;
|
import com.activiti.domain.idm.Group;
|
||||||
@@ -36,20 +39,20 @@ public class UserSyncService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private TenantFinderService tenantFinderService;
|
private TenantFinderService tenantFinderService;
|
||||||
|
|
||||||
@Value("${auth-ext.sync.externalId:oauth}")
|
@Value("${auth-ext.externalId:oauth}")
|
||||||
protected String externalIdmSource;
|
protected String externalIdmSource;
|
||||||
|
|
||||||
@Value("${auth-ext.sync.user.createMissing:true}")
|
@Value("${auth-ext.sync.user.createMissing:true}")
|
||||||
protected boolean createMissingUser;
|
protected boolean createMissingUser;
|
||||||
|
|
||||||
@Value("${auth-ext.sync.user.requireGroup:#{null}}")
|
@Value("${auth-ext.sync.user.requireOidcGroup:#{null}}")
|
||||||
protected String requiredGroup;
|
protected String requiredGroup;
|
||||||
|
|
||||||
@Value("${auth-ext.sync.user.clearNewUserGroups:true}")
|
@Value("${auth-ext.sync.user.clearNewUserGroups:true}")
|
||||||
protected boolean clearNewUserGroups;
|
protected boolean clearNewUserGroups;
|
||||||
|
|
||||||
public void sync(OidcUser oidcUser) {
|
public void sync(OidcUser oidcUser) {
|
||||||
UserDetails springUser = this.loadSpringUser(oidcUser);
|
UserDetails springUser = this.loadSpringUser(oidcUser.getEmail(), oidcUser.getGivenName(), oidcUser.getFamilyName(), oidcUser);
|
||||||
if (this.logger.isTraceEnabled()) {
|
if (this.logger.isTraceEnabled()) {
|
||||||
this.logger.trace("Loaded Spring Security user: {}: {}", springUser.getUsername(), springUser.getAuthorities());
|
this.logger.trace("Loaded Spring Security user: {}: {}", springUser.getUsername(), springUser.getAuthorities());
|
||||||
} else {
|
} else {
|
||||||
@@ -57,42 +60,58 @@ public class UserSyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserDetails loadSpringUser(OidcUser oidcUser) throws UsernameNotFoundException {
|
public void sync(Jwt jwt) {
|
||||||
|
String email = jwt.getClaim(StandardClaimNames.EMAIL);
|
||||||
|
if (email == null)
|
||||||
|
throw new IllegalArgumentException("An '" + StandardClaimNames.EMAIL + "' claim is required");
|
||||||
|
|
||||||
|
String givenName = jwt.getClaim(StandardClaimNames.GIVEN_NAME);
|
||||||
|
String familyName = jwt.getClaim(StandardClaimNames.FAMILY_NAME);
|
||||||
|
|
||||||
|
UserDetails springUser = this.loadSpringUser(email, givenName, familyName, jwt);
|
||||||
|
if (this.logger.isTraceEnabled()) {
|
||||||
|
this.logger.trace("Loaded Spring Security user: {}: {}", springUser.getUsername(), springUser.getAuthorities());
|
||||||
|
} else {
|
||||||
|
this.logger.debug("Loaded Spring Security user: {}", springUser.getUsername());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserDetails loadSpringUser(String email, String givenName, String familyName, ClaimAccessor claims) throws UsernameNotFoundException {
|
||||||
try {
|
try {
|
||||||
UserDetails springUser = this.userDetailsService.loadUserByUsername(oidcUser.getEmail());
|
UserDetails springUser = this.userDetailsService.loadUserByUsername(email);
|
||||||
this.logger.debug("Loaded APS user: {} => {}", oidcUser.getEmail(), springUser.getUsername());
|
this.logger.debug("Loaded APS user: {} => {}", email, springUser.getUsername());
|
||||||
return springUser;
|
return springUser;
|
||||||
} catch (UsernameNotFoundException unfe) {
|
} catch (UsernameNotFoundException unfe) {
|
||||||
this.logger.debug("User does not exist: {}", unfe.getMessage());
|
this.logger.debug("User does not exist: {}", unfe.getMessage());
|
||||||
if (!this.createMissingUser)
|
if (!this.createMissingUser)
|
||||||
throw unfe;
|
throw unfe;
|
||||||
|
|
||||||
if (this.requiredGroup != null && (!oidcUser.hasClaim("groups") || !oidcUser.getClaimAsStringList("groups").contains(this.requiredGroup))) {
|
if (this.requiredGroup != null && (!claims.hasClaim("groups") || !claims.getClaimAsStringList("groups").contains(this.requiredGroup))) {
|
||||||
this.logger.info("User does not exist and does not have the required OIDC group to be created: {} ", oidcUser.getEmail(), this.requiredGroup);
|
this.logger.info("User does not exist and does not have the required OIDC group to be created: {} ", email, this.requiredGroup);
|
||||||
throw unfe;
|
throw unfe;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.debug("User does not exist; will attempt to create: {}", oidcUser.getEmail());
|
this.logger.debug("User does not exist; will attempt to create: {}", email);
|
||||||
User apsUser = this.createApsUser(oidcUser);
|
User apsUser = this.createApsUser(email, givenName, familyName);
|
||||||
if (this.clearNewUserGroups) {
|
if (this.clearNewUserGroups) {
|
||||||
apsUser = this.userService.getUser(apsUser.getId(), true);
|
apsUser = this.userService.getUser(apsUser.getId(), true);
|
||||||
if (this.logger.isDebugEnabled())
|
if (this.logger.isDebugEnabled())
|
||||||
this.logger.debug("User is new; clearing default groups: {}: {}", oidcUser.getEmail(), apsUser.getGroups().stream().map(group -> group.getName()).toList());
|
this.logger.debug("User is new; clearing default groups: {}: {}", email, apsUser.getGroups().stream().map(group -> group.getName()).toList());
|
||||||
this.deleteApsUserGroups(apsUser);
|
this.deleteApsUserGroups(apsUser);
|
||||||
}
|
}
|
||||||
return this.userDetailsService.loadByUserId(apsUser.getId());
|
return this.userDetailsService.loadByUserId(apsUser.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private User createApsUser(OidcUser oidcUser) {
|
private User createApsUser(String email, String givenName, String familyName) {
|
||||||
long tenantId = this.tenantFinderService.findTenantId();
|
long tenantId = this.tenantFinderService.findTenantId();
|
||||||
|
|
||||||
User user = this.userService.createNewUserFromExternalStore(
|
User user = this.userService.createNewUserFromExternalStore(
|
||||||
oidcUser.getEmail(),
|
email,
|
||||||
oidcUser.getGivenName(),
|
givenName,
|
||||||
oidcUser.getFamilyName(),
|
familyName,
|
||||||
tenantId,
|
tenantId,
|
||||||
oidcUser.getEmail(),
|
email,
|
||||||
this.externalIdmSource,
|
this.externalIdmSource,
|
||||||
new Date());
|
new Date());
|
||||||
this.logger.info("Created user: {} => {}", user.getId(), user.getEmail());
|
this.logger.info("Created user: {} => {}", user.getId(), user.getEmail());
|
||||||
|
@@ -101,14 +101,14 @@
|
|||||||
"profile",
|
"profile",
|
||||||
"roles",
|
"roles",
|
||||||
"basic",
|
"basic",
|
||||||
"email"
|
"email",
|
||||||
|
"microprofile-jwt"
|
||||||
],
|
],
|
||||||
"optionalClientScopes": [
|
"optionalClientScopes": [
|
||||||
"address",
|
"address",
|
||||||
"phone",
|
"phone",
|
||||||
"organization",
|
"organization",
|
||||||
"offline_access",
|
"offline_access"
|
||||||
"microprofile-jwt"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -156,15 +156,76 @@
|
|||||||
"profile",
|
"profile",
|
||||||
"roles",
|
"roles",
|
||||||
"basic",
|
"basic",
|
||||||
"email"
|
"email",
|
||||||
|
"microprofile-jwt"
|
||||||
],
|
],
|
||||||
"optionalClientScopes": [
|
"optionalClientScopes": [
|
||||||
"address",
|
"address",
|
||||||
"phone",
|
"phone",
|
||||||
"organization",
|
"organization",
|
||||||
"offline_access",
|
"offline_access"
|
||||||
"microprofile-jwt"
|
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clientId": "cli",
|
||||||
|
"name": "Command Line Tools",
|
||||||
|
"description": "",
|
||||||
|
"rootUrl": "",
|
||||||
|
"adminUrl": "",
|
||||||
|
"baseUrl": "",
|
||||||
|
"surrogateAuthRequired": false,
|
||||||
|
"enabled": true,
|
||||||
|
"alwaysDisplayInConsole": false,
|
||||||
|
"clientAuthenticatorType": "client-secret",
|
||||||
|
"secret": "eJa5W7bv4ohFbr7QRtaCk0eccRFoYM5x",
|
||||||
|
"redirectUris": [
|
||||||
|
"/*"
|
||||||
|
],
|
||||||
|
"webOrigins": [
|
||||||
|
"/*"
|
||||||
|
],
|
||||||
|
"notBefore": 0,
|
||||||
|
"bearerOnly": false,
|
||||||
|
"consentRequired": false,
|
||||||
|
"standardFlowEnabled": false,
|
||||||
|
"implicitFlowEnabled": false,
|
||||||
|
"directAccessGrantsEnabled": false,
|
||||||
|
"serviceAccountsEnabled": true,
|
||||||
|
"publicClient": false,
|
||||||
|
"frontchannelLogout": true,
|
||||||
|
"protocol": "openid-connect",
|
||||||
|
"attributes": {
|
||||||
|
"realm_client": "false",
|
||||||
|
"oidc.ciba.grant.enabled": "false",
|
||||||
|
"client.secret.creation.time": "1747506410",
|
||||||
|
"backchannel.logout.session.required": "true",
|
||||||
|
"standard.token.exchange.enabled": "true",
|
||||||
|
"oauth2.device.authorization.grant.enabled": "false",
|
||||||
|
"backchannel.logout.revoke.offline.tokens": "false"
|
||||||
|
},
|
||||||
|
"authenticationFlowBindingOverrides": {},
|
||||||
|
"fullScopeAllowed": true,
|
||||||
|
"nodeReRegistrationTimeout": -1,
|
||||||
|
"defaultClientScopes": [
|
||||||
|
"web-origins",
|
||||||
|
"acr",
|
||||||
|
"profile",
|
||||||
|
"roles",
|
||||||
|
"basic",
|
||||||
|
"email",
|
||||||
|
"microprofile-jwt"
|
||||||
|
],
|
||||||
|
"optionalClientScopes": [
|
||||||
|
"address",
|
||||||
|
"phone",
|
||||||
|
"organization",
|
||||||
|
"offline_access"
|
||||||
|
],
|
||||||
|
"access": {
|
||||||
|
"view": true,
|
||||||
|
"configure": true,
|
||||||
|
"manage": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"users": [
|
"users": [
|
||||||
|
35
src/test/vscode/simple.http
Normal file
35
src/test/vscode/simple.http
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
@keycloakRealm = my-app
|
||||||
|
@keycloakBaseUrl = http://localhost:8081
|
||||||
|
@oauthUrl = {{keycloakBaseUrl}}/realms/{{keycloakRealm}}
|
||||||
|
@keycloakTokenUrl = {{oauthUrl}}/protocol/openid-connect/token
|
||||||
|
@oauthClientId = cli
|
||||||
|
@oauthClientSecret = eJa5W7bv4ohFbr7QRtaCk0eccRFoYM5x
|
||||||
|
@apsBaseUrl = http://localhost:8080/activiti-app
|
||||||
|
|
||||||
|
### Token
|
||||||
|
# @name token
|
||||||
|
curl -LX POST {{keycloakTokenUrl}} \
|
||||||
|
-H 'Content-type: application/x-www-form-urlencoded' \
|
||||||
|
-d "grant_type=client_credentials" \
|
||||||
|
-d "client_id={{oauthClientId}}" \
|
||||||
|
-d "client_secret={{oauthClientSecret}}"
|
||||||
|
|
||||||
|
@accessToken = {{token.response.body.access_token}}
|
||||||
|
@auth = Bearer {{accessToken}}
|
||||||
|
|
||||||
|
### APS Version
|
||||||
|
# @name version
|
||||||
|
GET {{apsBaseUrl}}/api/enterprise/app-version
|
||||||
|
Authorization: Bearer {{accessToken}}
|
||||||
|
|
||||||
|
### APS Tenants
|
||||||
|
# @name tenants
|
||||||
|
GET {{apsBaseUrl}}/api/enterprise/admin/tenants
|
||||||
|
Authorization: Bearer {{accessToken}}
|
||||||
|
|
||||||
|
@tenantId = {{tenants.response.body.0.id}}
|
||||||
|
|
||||||
|
### APS Templates
|
||||||
|
# @name templates
|
||||||
|
GET {{apsBaseUrl}}/app/rest/document-templates?tenantId={{tenantId}}&start=0&size=10&sort=sort_by_name_asc
|
||||||
|
Authorization: Bearer {{accessToken}}
|
Reference in New Issue
Block a user