diff --git a/.env b/.env
index 4aa43c3ed..0839976e7 100644
--- a/.env
+++ b/.env
@@ -1,3 +1,3 @@
# Used in docker-compose
# shellcheck disable=SC2034
-KEYCLOAK_VERSION=21.0.1
+KEYCLOAK_VERSION=21.1.1
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index e2eabd9ba..b3e0706d9 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -30,7 +30,7 @@ jobs:
- KEYCLOAK_VERSION: 18.0.2
- KEYCLOAK_VERSION: 19.0.3
- KEYCLOAK_VERSION: 20.0.5
- - KEYCLOAK_VERSION: 21.0.1
+ - KEYCLOAK_VERSION: 21.1.1
steps:
- uses: actions/checkout@v3
with:
@@ -62,14 +62,14 @@ jobs:
run: echo "::set-output name=VERSION::$(tail -n1 .env | cut -d= -f2)"
- name: Login to Docker Hub
- uses: docker/login-action@v2.1.0
+ uses: docker/login-action@v2.2.0
if: startsWith(github.event.ref, 'refs/tags/v')
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to Quay.io
- uses: docker/login-action@v2.1.0
+ uses: docker/login-action@v2.2.0
if: startsWith(github.event.ref, 'refs/tags/v')
with:
registry: quay.io
@@ -81,7 +81,7 @@ jobs:
- name: Set up Docker Build Metadata
id: docker_meta
- uses: crazy-max/ghaction-docker-meta@v4.3.0
+ uses: crazy-max/ghaction-docker-meta@v4.6.0
with:
images: adorsys/keycloak-config-cli,quay.io/adorsys/keycloak-config-cli
flavor: |
@@ -102,7 +102,7 @@ jobs:
uses: docker/setup-buildx-action@v2
- name: Build and push
- uses: docker/build-push-action@v4.0.0
+ uses: docker/build-push-action@v4.1.1
with:
build-args: |-
KEYCLOAK_VERSION=${{ matrix.env.KEYCLOAK_VERSION }}
@@ -161,7 +161,7 @@ jobs:
fail-fast: false
matrix:
env:
- - KEYCLOAK_VERSION: 21.0.1
+ - KEYCLOAK_VERSION: 21.1.1
steps:
- uses: actions/checkout@v3
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ef5befff9..5d97a2370 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- copy the JSON array of the old `userProfile` property to the new `userProfile.attributes` property
- create a new JSON array for the `userProfile.groups` property (containing the attribute groups definitions)
- in the end, the `userProfile` property should match the content of the "JSON editor" tab in the "Realm settings > User profile" page from the Keycloak admin console
+- Add support for managing client-policies
## [5.6.1] - 2023-03-05
diff --git a/Dockerfile b/Dockerfile
index d23b757cc..b8491f608 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,7 +6,7 @@ FROM ${BUILDER_IMAGE} AS BUILDER
WORKDIR /app/
-ARG KEYCLOAK_VERSION=21.0.1
+ARG KEYCLOAK_VERSION=21.1.1
ARG MAVEN_CLI_OPTS="-ntp -B"
COPY .mvn .mvn
diff --git a/docs/FEATURES.md b/docs/FEATURES.md
index 1c5a7bb4e..3c2e6c4ed 100644
--- a/docs/FEATURES.md
+++ b/docs/FEATURES.md
@@ -55,6 +55,7 @@
| Remove clientScopeMappings | 2.5.0 | Remove existing clientScopeMappings while creating or updating realms |
| Synchronize user federation | 3.5.0 | Synchronize the user federation defined on the realm configuration |
| Synchronize user profile | 5.4.0 | Synchronize the user profile configuration defined on the realm configuration |
+| Synchronize client-policies | 5.6.0 | Synchronize the client-policies (clientProfiles and clientPolicies) while updating realms |
# Specificities
diff --git a/pom.xml b/pom.xml
index 988efe2a4..7a2a55e08 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
org.springframework.boot
spring-boot-starter-parent
- 2.7.9
+ 2.7.12
@@ -59,7 +59,7 @@
UTF-8
UTF-8
- 21.0.1
+ 21.1.1
3.2.0
10.0
@@ -68,28 +68,29 @@
1.10.0
2.4.4
4.9.10
- 2.13.4
- 0.8.9
- 2.0.0
+ 2.13.5
+ 0.8.10
+ 2.0.1
1.1.2
2.1.1
2.0.0
- 7.3
+ 7.4
3.0.0-M5
- 3.0.0
+ 3.0.1
1.5.3
- 2.0.0
+ 2.0.1
3.0.0-M5
5.15.0
- 3.20.0
+ 3.21.0
6.55.0
0.15
5.0.4.Final
- 4.7.3.3
+ 4.7.3.5
4.7.3
- 1.17.6
- 6.0.8
+ 1.18.3
+ 6.0.9
2.27.2
+ 3.24.2
keycloak-config-cli
adorsys
@@ -176,6 +177,7 @@
org.springframework.security
spring-security-crypto
+ 5.8.3
@@ -189,6 +191,12 @@
spring-boot-loader
+
+ org.yaml
+ snakeyaml
+ 2.0
+
+
org.keycloak
keycloak-admin-client
@@ -230,11 +238,6 @@
jackson-databind
-
- org.yaml
- snakeyaml
-
-
net.logstash.logback
@@ -287,6 +290,13 @@
test
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
org.mock-server
mockserver-spring-test-listener
diff --git a/src/main/java/de/adorsys/keycloak/config/repository/ClientPoliciesRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/ClientPoliciesRepository.java
new file mode 100644
index 000000000..157e7e607
--- /dev/null
+++ b/src/main/java/de/adorsys/keycloak/config/repository/ClientPoliciesRepository.java
@@ -0,0 +1,112 @@
+/*-
+ * ---license-start
+ * keycloak-config-cli
+ * ---
+ * Copyright (C) 2017 - 2021 adorsys GmbH & Co. KG @ https://adorsys.com
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package de.adorsys.keycloak.config.repository;
+
+import de.adorsys.keycloak.config.model.RealmImport;
+import org.keycloak.admin.client.resource.ClientPoliciesPoliciesResource;
+import org.keycloak.admin.client.resource.ClientPoliciesProfilesResource;
+import org.keycloak.representations.idm.ClientPoliciesRepresentation;
+import org.keycloak.representations.idm.ClientProfilesRepresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ClientPoliciesRepository {
+
+ private static final Logger logger = LoggerFactory.getLogger(AuthenticationFlowRepository.class);
+
+ private final RealmRepository realmRepository;
+
+ @Autowired
+ public ClientPoliciesRepository(RealmRepository realmRepository) {
+ this.realmRepository = realmRepository;
+ }
+
+ private ClientPoliciesPoliciesResource getPoliciesResource(String realmName) {
+ return this.realmRepository.getResource(realmName).clientPoliciesPoliciesResource();
+ }
+
+ private ClientPoliciesProfilesResource getProfilesResource(String realmName) {
+ return this.realmRepository.getResource(realmName).clientPoliciesProfilesResource();
+ }
+
+ public void updateClientPoliciesPolicies(RealmImport realmImport, ClientPoliciesRepresentation newClientPolicies) {
+
+ ClientPoliciesPoliciesResource policiesResource = getPoliciesResource(realmImport.getRealm());
+
+ ClientPoliciesRepresentation existingClientPolicies;
+ try {
+ existingClientPolicies = policiesResource.getPolicies();
+ } catch (Exception ex) {
+ existingClientPolicies = null;
+ }
+
+ if (existingClientPolicies == null && newClientPolicies == null) {
+ logger.trace("No client-policy policies configured, skipping update.");
+ return;
+ }
+
+ if (existingClientPolicies != null && existingClientPolicies.equals(newClientPolicies)) {
+ logger.trace("Current client-policy policies match existing policies, skipping update.");
+ return;
+ }
+
+ if (newClientPolicies == null) {
+ logger.trace("New client-policy policies resets existing policies.");
+ newClientPolicies = new ClientPoliciesRepresentation();
+ }
+
+ policiesResource.updatePolicies(newClientPolicies);
+ }
+
+ public void updateClientPoliciesProfiles(RealmImport realmImport, ClientProfilesRepresentation newClientProfiles) {
+
+ ClientPoliciesProfilesResource profilesResource = getProfilesResource(realmImport.getRealm());
+
+ // Note that we deliberately ignore global profiles, to avoid inconsistencies.
+ ClientProfilesRepresentation existingClientProfiles;
+ try {
+ existingClientProfiles = profilesResource.getProfiles(false);
+ } catch (Exception ex) {
+ existingClientProfiles = null;
+ }
+
+ if (existingClientProfiles == null && newClientProfiles == null) {
+ logger.trace("No client-policy profiles configured, skipping update.");
+ return;
+ }
+
+ if (existingClientProfiles != null && existingClientProfiles.equals(newClientProfiles)) {
+ logger.trace("Current client-policy profiles match existing profiles, skipping update.");
+ return;
+ }
+
+ if (newClientProfiles == null) {
+ logger.trace("New client-policy profiles resets existing profiles.");
+ newClientProfiles = new ClientProfilesRepresentation();
+ }
+
+ profilesResource.updateProfiles(newClientProfiles);
+ }
+
+}
diff --git a/src/main/java/de/adorsys/keycloak/config/service/ClientPoliciesImportService.java b/src/main/java/de/adorsys/keycloak/config/service/ClientPoliciesImportService.java
new file mode 100644
index 000000000..f660d86ff
--- /dev/null
+++ b/src/main/java/de/adorsys/keycloak/config/service/ClientPoliciesImportService.java
@@ -0,0 +1,55 @@
+/*-
+ * ---license-start
+ * keycloak-config-cli
+ * ---
+ * Copyright (C) 2017 - 2021 adorsys GmbH & Co. KG @ https://adorsys.com
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package de.adorsys.keycloak.config.service;
+
+import de.adorsys.keycloak.config.model.RealmImport;
+import de.adorsys.keycloak.config.repository.ClientPoliciesRepository;
+import org.keycloak.representations.idm.ClientPoliciesRepresentation;
+import org.keycloak.representations.idm.ClientProfilesRepresentation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ClientPoliciesImportService {
+
+ private static final Logger logger = LoggerFactory.getLogger(ClientPoliciesImportService.class);
+
+ private final ClientPoliciesRepository clientPoliciesRepository;
+
+ @Autowired
+ public ClientPoliciesImportService(ClientPoliciesRepository clientPoliciesRepository) {
+ this.clientPoliciesRepository = clientPoliciesRepository;
+ }
+
+ public void doImport(RealmImport realmImport) {
+
+ // client-profile profiles must be imported before client-profile policies
+ ClientProfilesRepresentation parsedClientProfiles = realmImport.getParsedClientProfiles();
+ clientPoliciesRepository.updateClientPoliciesProfiles(realmImport, parsedClientProfiles);
+ logger.trace("Updated client-policy profiles.");
+
+ ClientPoliciesRepresentation parsedClientPolicies = realmImport.getParsedClientPolicies();
+ clientPoliciesRepository.updateClientPoliciesPolicies(realmImport, parsedClientPolicies);
+ logger.trace("Updated client-policy policies.");
+ }
+}
diff --git a/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java b/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java
index ebb0543d2..63c928d45 100644
--- a/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java
+++ b/src/main/java/de/adorsys/keycloak/config/service/RealmImportService.java
@@ -56,6 +56,8 @@ public class RealmImportService {
"requiredActions",
"defaultDefaultClientScopes",
"defaultOptionalClientScopes",
+ "clientProfiles",
+ "clientPolicies",
};
private static final Logger logger = LoggerFactory.getLogger(RealmImportService.class);
@@ -64,6 +66,9 @@ public class RealmImportService {
private final UserImportService userImportService;
private final UserProfileImportService userProfileImportService;
+
+ private final ClientPoliciesImportService clientPoliciesImportService;
+
private final RoleImportService roleImportService;
private final ClientImportService clientImportService;
private final ClientScopeImportService clientScopeImportService;
@@ -90,6 +95,7 @@ public RealmImportService(
RealmRepository realmRepository,
UserImportService userImportService,
UserProfileImportService userProfileImportService,
+ ClientPoliciesImportService clientPoliciesImportService,
RoleImportService roleImportService,
ClientImportService clientImportService,
GroupImportService groupImportService,
@@ -110,6 +116,7 @@ public RealmImportService(
this.realmRepository = realmRepository;
this.userImportService = userImportService;
this.userProfileImportService = userProfileImportService;
+ this.clientPoliciesImportService = clientPoliciesImportService;
this.roleImportService = roleImportService;
this.clientImportService = clientImportService;
this.groupImportService = groupImportService;
@@ -194,6 +201,7 @@ private void configureRealm(RealmImport realmImport, RealmRepresentation existin
defaultGroupsImportService.doImport(realmImport);
componentImportService.doImport(realmImport);
userProfileImportService.doImport(realmImport);
+ clientPoliciesImportService.doImport(realmImport);
userImportService.doImport(realmImport);
requiredActionsImportService.doImport(realmImport);
authenticationFlowsImportService.doImport(realmImport);
diff --git a/src/main/java/de/adorsys/keycloak/config/util/resteasy/CookieClientFilter.java b/src/main/java/de/adorsys/keycloak/config/util/resteasy/CookieClientFilter.java
index 4608b6aef..d8a3109ad 100644
--- a/src/main/java/de/adorsys/keycloak/config/util/resteasy/CookieClientFilter.java
+++ b/src/main/java/de/adorsys/keycloak/config/util/resteasy/CookieClientFilter.java
@@ -34,16 +34,17 @@
// Currently, this filter does not valide cookie or is able to remove cookies.
// A cookie managed is required to handle sticky sessions at cookie base
public class CookieClientFilter implements ClientRequestFilter, ClientResponseFilter {
- private final Map cookies = new HashMap<>();
+ private final ThreadLocal