diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a75d132b..25a299a0 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,13 +10,14 @@ jobs:
runs-on: ubuntu-22.04
services:
keycloak:
- image: quay.io/keycloak/keycloak:19.0.3-legacy
+ image: bitnami/keycloak:24.0.3
env:
KEYCLOAK_USER: keycloak
KEYCLOAK_PASSWORD: keycloak1!
- DB_VENDOR: h2
- ports: [ 8443/tcp ]
- options: --health-cmd "curl -Iks https://localhost:8443" --health-interval 10s --health-timeout 5s --health-retries 5
+ KEYCLOAK_DATABASE_VENDOR: dev-mem
+ KEYCLOAK_HTTP_RELATIVE_PATH: /auth
+ ports: [ 8080/tcp ]
+ options: --health-cmd "curl -Iks http://localhost:8080" --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- name: Checkout
uses: actions/checkout@v4
diff --git a/README.md b/README.md
index c1f67999..39aa88ea 100644
--- a/README.md
+++ b/README.md
@@ -19,13 +19,14 @@ This plugin provides the basis for using Keycloak as Identity Management solutio
**Beware: in case you want to use Keycloak's advanced login capabilities for social connections you must configure SSO as well.**
Password grant exchanges are only supported for Keycloak's internally managed users and users of an LDAP / Keberos User federation. Hence without SSO you will only be able to login with users managed by such connections.
-Current version: `7.21.0`
-Latest tests with: Keycloak `22.0.4`, `19.0.3-legacy`, Camunda `7.20.0`, `7.20.0-ee`
+Current version: `7.21.1`
+Latest tests with: Keycloak `24.0.3`, `19.0.3-legacy`, Camunda `7.21.0`, `7.21.0-ee`
#### Features
-Changes in version `7.21.0`
+Changes in version `7.21.1`
-* Upgrade to Camunda Platform 7.21.0
+* Upgrade to Camunda Platform 7.21.0
+* New configuration flag `enforceSubgroupsInGroupQuery` for enforcing subgroups in query results when using Keycloak >= `23.0.0`
Changes in version `7.20.1`
@@ -190,24 +191,25 @@ The `admin-user` part must be deleted in order to work properly. The recommended
A list of configuration options can be found below:
-| *Property* | *Description* |
-| --- | --- |
-| `keycloakIssuerUrl` | The basic issuer URL of your Keycloak server including the realm.
Sample for master realm: `https:///auth/realms/master` |
-| `keycloakAdminUrl` | The admin URL of the Keycloak server REST API including the realm.
Sample for master realm: `https:///auth/admin/realms/master` |
-| `clientId` | The Client ID of your application. |
-| `clientSecret` | The Client Secret of your application. |
-| `useEmailAsCamundaUserId` | Whether to use the Keycloak email attribute as Camunda's user ID. Default is `false`.
This is option is a fallback in case you don't use SSO and want to login using Camunda's web interface with your mail address and not the cryptic internal Keycloak ID. Keep in mind that you will only be able to login without SSO with Keycloak's internally managed users and users managed by the LDAP / Keberos User federation.|
-| `useUsernameAsCamundaUserId` | Whether to use the Keycloak username attribute as Camunda's user ID. Default is `false`. In the default case the plugin will use the internal Keycloak ID as Camunda's user ID.|
-| `useGroupPathAsCamundaGroupId` | Whether to use the Keycloak unique group path as Camunda's group ID. Default is `false`. In the default case the plugin will use the internal Keycloak ID as Camunda's group ID.
This flag is particularly useful in case you want to have human readable group IDs and recommended when using groups in Camunda's authorization management.
*Since 1.1.0* |
-| `administratorGroupName` | The name of the administrator group. If this name is set and engine authorization is enabled, the plugin will create group-level Administrator authorizations on all built-in resources. |
-| `administratorUserId` | The ID of the administrator user. If this ID is set and engine authorization is enabled, the plugin will create user-level Administrator authorizations on all built-in resources. |
-| `authorizationCheckEnabled` | If this property is set to true, then authorization checks are performed when querying for users or groups. Otherwise authorization checks are not performed when querying for users or groups. Default: `true`.
*Note*: If you have a huge amount of Keycloak users or groups we advise to set this property to false to improve the performance of the user and group query. |
-| `maxResultSize`| Maximum result size of queries against the Keycloak API. Default: `250`.
*Beware*: Setting the parameter to a too low value can lead to unexpected effects. Keep in mind that parts of the filtering takes place on the client side / within the plugin itself. Setting the parameter to a too high value can lead to performance and memory issues.
*Since 1.5.0* |
-| `maxHttpConnections` | Maximum number HTTP connections for the Keycloak connection pool. Default: `50`|
-| `disableSSLCertificateValidation` | Whether to disable SSL certificate validation. Default: `false`. Useful in test environments. |
-| `proxyUri` | Optional URI of a proxy to use. Default: `null`, example: `http://proxy:81`.
*Since 2.0.0* |
-| `proxyUser` | Optional username for proxy authentication. Default: `null`.
*Since 2.0.0* |
-| `proxyPassword` | Optional password for proxy authentication. Default: `null`.
*Since 2.0.0* |
+| *Property* | *Description* |
+|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `keycloakIssuerUrl` | The basic issuer URL of your Keycloak server including the realm.
Sample for master realm: `https:///auth/realms/master` |
+| `keycloakAdminUrl` | The admin URL of the Keycloak server REST API including the realm.
Sample for master realm: `https:///auth/admin/realms/master` |
+| `clientId` | The Client ID of your application. |
+| `clientSecret` | The Client Secret of your application. |
+| `useEmailAsCamundaUserId` | Whether to use the Keycloak email attribute as Camunda's user ID. Default is `false`.
This is option is a fallback in case you don't use SSO and want to login using Camunda's web interface with your mail address and not the cryptic internal Keycloak ID. Keep in mind that you will only be able to login without SSO with Keycloak's internally managed users and users managed by the LDAP / Keberos User federation. |
+| `useUsernameAsCamundaUserId` | Whether to use the Keycloak username attribute as Camunda's user ID. Default is `false`. In the default case the plugin will use the internal Keycloak ID as Camunda's user ID. |
+| `useGroupPathAsCamundaGroupId` | Whether to use the Keycloak unique group path as Camunda's group ID. Default is `false`. In the default case the plugin will use the internal Keycloak ID as Camunda's group ID.
This flag is particularly useful in case you want to have human readable group IDs and recommended when using groups in Camunda's authorization management.
*Since 1.1.0* |
+| `enforceSubgroupsInGroupQuery` | Starting with Keycloak version 23 the group query without any other search parameters does not automatically return subgroups within the result. Set this flag to `true` in case you use subgroups together with Keycloak 23 or higher. Otherwise leave it to the default `false` and benefit from better performance.
*Since 7.21.1* |
+| `administratorGroupName` | The name of the administrator group. If this name is set and engine authorization is enabled, the plugin will create group-level Administrator authorizations on all built-in resources. |
+| `administratorUserId` | The ID of the administrator user. If this ID is set and engine authorization is enabled, the plugin will create user-level Administrator authorizations on all built-in resources. |
+| `authorizationCheckEnabled` | If this property is set to true, then authorization checks are performed when querying for users or groups. Otherwise authorization checks are not performed when querying for users or groups. Default: `true`.
*Note*: If you have a huge amount of Keycloak users or groups we advise to set this property to false to improve the performance of the user and group query. |
+| `maxResultSize` | Maximum result size of queries against the Keycloak API. Default: `250`.
*Beware*: Setting the parameter to a too low value can lead to unexpected effects. Keep in mind that parts of the filtering takes place on the client side / within the plugin itself. Setting the parameter to a too high value can lead to performance and memory issues.
*Since 1.5.0* |
+| `maxHttpConnections` | Maximum number HTTP connections for the Keycloak connection pool. Default: `50` |
+| `disableSSLCertificateValidation` | Whether to disable SSL certificate validation. Default: `false`. Useful in test environments. |
+| `proxyUri` | Optional URI of a proxy to use. Default: `null`, example: `http://proxy:81`.
*Since 2.0.0* |
+| `proxyUser` | Optional username for proxy authentication. Default: `null`.
*Since 2.0.0* |
+| `proxyPassword` | Optional password for proxy authentication. Default: `null`.
*Since 2.0.0* |
@@ -425,20 +427,17 @@ version: "3.9"
services:
jboss.keycloak:
- image: quay.io/keycloak/keycloak:22.0.4
+ image: bitnami/keycloak:24.0.3
restart: unless-stopped
environment:
TZ: Europe/Berlin
- DB_VENDOR: h2
+ KEYCLOAK_DATABASE_VENDOR: dev-mem
KEYCLOAK_ADMIN: keycloak
KEYCLOAK_ADMIN_PASSWORD: keycloak1!
- KC_HTTP_RELATIVE_PATH: /auth
+ KEYCLOAK_HTTP_RELATIVE_PATH: /auth
ports:
- "8443:8443"
- "8080:8080"
- command:
- - start-dev
- - --features admin-fine-grained-authz
```
For details see documentation on [Running Keycloak in a container](https://www.keycloak.org/server/containers "Running Keycloak in a container").
@@ -447,12 +446,12 @@ For details see documentation on [Running Keycloak in a container](https://www.k
Running unit tests from Maven requires configuring the details of a running Keycloak server. This can be achieved by setting the following environment variables:
-| *Environment Variable* | *Description* |
-| --- | --- |
-| `KEYCLOAK_URL` | Keycloak server URL.
Default value: `http://localhost:8080/auth` |
-| `KEYCLOAK_ADMIN_USER` | The admin user of the Keycloak server.
Default value: `keycloak` |
-| `KEYCLOAK_ADMIN_PASSWORD` | The admin password of the Keycloak server.
Default value: `keycloak1!` |
-
+| *Environment Variable* | *Description* |
+| --- |-----------------------------------------------------------------------------------------------------------------------|
+| `KEYCLOAK_URL` | Keycloak server URL.
Default value: `http://localhost:8080/auth` |
+| `KEYCLOAK_ADMIN_USER` | The admin user of the Keycloak server.
Default value: `keycloak` |
+| `KEYCLOAK_ADMIN_PASSWORD` | The admin password of the Keycloak server.
Default value: `keycloak1!` |
+ | `KEYCLOAK_ENFORCE_SUBGROUPS_IN_GROUP_QUERY` | Wether to enforce subgroup results in group queries when testing with Keycloak >= `23.0.0`
Default value: `true` |
In case you choose Keycloak in the new Quarkus distribution, please be aware that `/auth` has been removed from the default context path.
Hence, it is required to change the `KEYCLOAK_URL` for the tests. Tests also run successfully against the Quarkus
distribution, in case you start Keycloak in Development mode.
diff --git a/extension/src/main/java/org/camunda/bpm/extension/keycloak/KeycloakConfiguration.java b/extension/src/main/java/org/camunda/bpm/extension/keycloak/KeycloakConfiguration.java
index 422599f9..5f089955 100644
--- a/extension/src/main/java/org/camunda/bpm/extension/keycloak/KeycloakConfiguration.java
+++ b/extension/src/main/java/org/camunda/bpm/extension/keycloak/KeycloakConfiguration.java
@@ -37,6 +37,16 @@ public class KeycloakConfiguration {
*/
protected boolean useGroupPathAsCamundaGroupId = false;
+ /**
+ * Starting with Keycloak version 23.x the group query without any other search parameters does not automatically
+ * return subgroups within the result. Generally this has positive effects on performance and in case you do not
+ * have subgroups you must not take care. On the other hand, if you do use subgroups you'll miss information and get
+ * inconsistent results.
+ *
+ * Set this flag to 'true' in case you use subgroups together with Keycloak 23 or higher.
+ */
+ protected boolean enforceSubgroupsInGroupQuery = false;
+
/** The name of the administrator group.
*
* If this name is set to a non-null and non-empty value,
@@ -207,6 +217,20 @@ public void setUseGroupPathAsCamundaGroupId(boolean useGroupPathAsCamundaGroupId
this.useGroupPathAsCamundaGroupId = useGroupPathAsCamundaGroupId;
}
+ /**
+ * @return the enforceSubgroupsInGroupQuery
+ */
+ public boolean isEnforceSubgroupsInGroupQuery() {
+ return enforceSubgroupsInGroupQuery;
+ }
+
+ /**
+ * @param enforceSubgroupsInGroupQuery the enforceSubgroupsInGroupQuery to set
+ */
+ public void setEnforceSubgroupsInGroupQuery(boolean enforceSubgroupsInGroupQuery) {
+ this.enforceSubgroupsInGroupQuery = enforceSubgroupsInGroupQuery;
+ }
+
/**
* @return the administratorGroupName
*/
diff --git a/extension/src/main/java/org/camunda/bpm/extension/keycloak/KeycloakGroupService.java b/extension/src/main/java/org/camunda/bpm/extension/keycloak/KeycloakGroupService.java
index 864c622e..e9fc1543 100644
--- a/extension/src/main/java/org/camunda/bpm/extension/keycloak/KeycloakGroupService.java
+++ b/extension/src/main/java/org/camunda/bpm/extension/keycloak/KeycloakGroupService.java
@@ -255,13 +255,20 @@ private boolean isValid(KeycloakGroupQuery query, Group group, StringBuilder res
*/
private String createGroupSearchFilter(CacheableKeycloakGroupQuery query) {
StringBuilder filter = new StringBuilder();
+ boolean hasSearch = false;
if (StringUtils.hasLength(query.getName())) {
+ hasSearch = true;
addArgument(filter, "search", query.getName());
}
if (StringUtils.hasLength(query.getNameLike())) {
+ hasSearch = true;
addArgument(filter, "search", query.getNameLike().replaceAll("[%,\\*]", ""));
}
addArgument(filter, "max", getMaxQueryResultSize());
+ if (!hasSearch && keycloakConfiguration.isEnforceSubgroupsInGroupQuery()) {
+ // fix: include subgroups in query result for Keycloak >= 23
+ addArgument(filter, "q", ":");
+ }
if (filter.length() > 0) {
filter.insert(0, "?");
String result = filter.toString();
diff --git a/extension/src/test/java/org/camunda/bpm/extension/keycloak/test/AbstractKeycloakIdentityProviderTest.java b/extension/src/test/java/org/camunda/bpm/extension/keycloak/test/AbstractKeycloakIdentityProviderTest.java
index 65ba26b3..2417c72c 100644
--- a/extension/src/test/java/org/camunda/bpm/extension/keycloak/test/AbstractKeycloakIdentityProviderTest.java
+++ b/extension/src/test/java/org/camunda/bpm/extension/keycloak/test/AbstractKeycloakIdentityProviderTest.java
@@ -50,6 +50,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends PluggableProc
private static final String KEYCLOAK_URL; // expected format "https:///auth
private static final String KEYCLOAK_ADMIN_USER;
private static final String KEYCLOAK_ADMIN_PASSWORD;
+ private static final Boolean KEYCLOAK_ENFORCE_SUBGROUPS_IN_GROUP_QUERY;
// ------------------------------------------------------------------------
@@ -86,6 +87,8 @@ public abstract class AbstractKeycloakIdentityProviderTest extends PluggableProc
KEYCLOAK_URL = getConfigValue(defaults, "keycloak.url").replaceAll("/+$", "");
KEYCLOAK_ADMIN_USER = getConfigValue(defaults, "keycloak.admin.user");
KEYCLOAK_ADMIN_PASSWORD = getConfigValue(defaults, "keycloak.admin.password");
+ KEYCLOAK_ENFORCE_SUBGROUPS_IN_GROUP_QUERY =
+ Boolean.valueOf(getConfigValue(defaults, "keycloak.enforce.subgroups.in.group.query"));
// setup
try {
@@ -168,6 +171,7 @@ protected static KeycloakIdentityProviderPlugin configureKeycloakIdentityProvide
KeycloakIdentityProviderPlugin kcp = (KeycloakIdentityProviderPlugin) p;
kcp.setKeycloakAdminUrl(kcp.getKeycloakAdminUrl().replace("http://localhost:9000", KEYCLOAK_URL));
kcp.setKeycloakIssuerUrl(kcp.getKeycloakIssuerUrl().replace("http://localhost:9000", KEYCLOAK_URL));
+ kcp.setEnforceSubgroupsInGroupQuery(KEYCLOAK_ENFORCE_SUBGROUPS_IN_GROUP_QUERY);
kcp.setClientSecret(CLIENT_SECRET);
return kcp;
}
@@ -529,7 +533,7 @@ static String createGroup(HttpHeaders headers, String realm, String groupName, b
* Deletes a group.
* @param headers HttpHeaders including the Authorization header / acces token
* @param realm the realm name
- * @param userId the user ID
+ * @param groupId the group ID
*/
static void deleteGroup(HttpHeaders headers, String realm, String groupId) {
ResponseEntity response = restTemplate.exchange(KEYCLOAK_URL + "/admin/realms/" + realm + "/groups/" + groupId, HttpMethod.DELETE, new HttpEntity<>(headers), String.class);
diff --git a/extension/src/test/java/org/camunda/bpm/extension/keycloak/test/KeycloakMassDataTest.java b/extension/src/test/java/org/camunda/bpm/extension/keycloak/test/KeycloakMassDataTest.java
index 8ce6bbbf..41c08639 100644
--- a/extension/src/test/java/org/camunda/bpm/extension/keycloak/test/KeycloakMassDataTest.java
+++ b/extension/src/test/java/org/camunda/bpm/extension/keycloak/test/KeycloakMassDataTest.java
@@ -35,18 +35,23 @@ protected void setUp() throws Exception {
for (int i = 0; i < 60; i++) {
USER_IDS.add(createUser(headers, realm, "test.user" + i, "Test" + i, "User Test" + i, "test.user" + i + "@test.info", "test"));
}
+ headers = authenticateKeycloakAdmin();
for (int i = 0; i < 100; i++) {
USER_IDS.add(createUser(headers, realm, "user.test" + i, "UTest" + i, "User Test" + i, "utest.user" + i + "@test.info", "test"));
}
- USER_IDS.forEach(u -> assignUserGroup(headers, realm, u, GROUP_ID_MANAGER));
-
+ HttpHeaders finalHeaders = headers;
+ USER_IDS.forEach(u -> assignUserGroup(finalHeaders, realm, u, GROUP_ID_MANAGER));
+
+ headers = authenticateKeycloakAdmin();
for (int i = 0; i < 60; i++) {
GROUP_IDS.add(createGroup(headers, realm, "test.group" + i, false));
}
+ headers = authenticateKeycloakAdmin();
for (int i = 0; i < 100; i++) {
GROUP_IDS.add(createGroup(headers, realm, "group.test" + i, false));
}
- GROUP_IDS.forEach(g -> assignUserGroup(headers, realm, USER_ID_TEAMLEAD, g));
+ HttpHeaders finalHeaders1 = headers;
+ GROUP_IDS.forEach(g -> assignUserGroup(finalHeaders1, realm, USER_ID_TEAMLEAD, g));
// setup process engine
// -------------------------------------
diff --git a/extension/src/test/resources/keycloak-default.properties b/extension/src/test/resources/keycloak-default.properties
index 5ab40b0b..41477c04 100644
--- a/extension/src/test/resources/keycloak-default.properties
+++ b/extension/src/test/resources/keycloak-default.properties
@@ -1,3 +1,4 @@
-keycloak.url=https://localhost:8443/auth
+keycloak.url=http://localhost:8080/auth
keycloak.admin.user=keycloak
-keycloak.admin.password=keycloak1!
\ No newline at end of file
+keycloak.admin.password=keycloak1!
+keycloak.enforce.subgroups.in.group.query=true
\ No newline at end of file