From cdc5d8fff89a7ddbdecd5b94185667a7655cc691 Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 30 Jan 2024 18:32:19 +0100 Subject: [PATCH] Migrating Realm JSON with declarative user profile fails when scope selectors present on any attributes closes #26266 Signed-off-by: mposolda --- .../migration/migrators/MigrateTo23_0_0.java | 26 +++++++------- .../JsonFileImport1903MigrationTest.java | 35 +++++++++++++++++++ .../migration-realm-19.0.3-user-profile.json | 26 ++++++++++++++ 3 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-19.0.3-user-profile.json diff --git a/model/storage-private/src/main/java/org/keycloak/migration/migrators/MigrateTo23_0_0.java b/model/storage-private/src/main/java/org/keycloak/migration/migrators/MigrateTo23_0_0.java index ad3b79973c..44bfc92679 100644 --- a/model/storage-private/src/main/java/org/keycloak/migration/migrators/MigrateTo23_0_0.java +++ b/model/storage-private/src/main/java/org/keycloak/migration/migrators/MigrateTo23_0_0.java @@ -44,26 +44,24 @@ public class MigrateTo23_0_0 implements Migration { @Override public void migrate(KeycloakSession session) { - session.realms().getRealmsStream().forEach(realm -> { - KeycloakContext context = session.getContext(); - - try { - context.setRealm(realm); - migrateRealm(realm); - } finally { - context.setRealm(null); - } - }); + session.realms().getRealmsStream().forEach(realm -> migrateRealm(session, realm)); } @Override public void migrateImport(KeycloakSession session, RealmModel realm, RealmRepresentation rep, boolean skipUserDependent) { - migrateRealm(realm); + migrateRealm(session, realm); } - private void migrateRealm(RealmModel realm) { - updateUserProfileConfig(realm); - removeRegistrationProfileFormExecution(realm); + private void migrateRealm(KeycloakSession session, RealmModel realm) { + KeycloakContext context = session.getContext(); + + try { + context.setRealm(realm); + updateUserProfileConfig(realm); + removeRegistrationProfileFormExecution(realm); + } finally { + context.setRealm(null); + } } private void updateUserProfileConfig(RealmModel realm) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport1903MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport1903MigrationTest.java index cd9d3b82a3..5de6d14b47 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport1903MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport1903MigrationTest.java @@ -17,14 +17,26 @@ package org.keycloak.testsuite.migration; import org.junit.Test; +import org.keycloak.OAuth2Constants; import org.keycloak.exportimport.util.ImportUtils; +import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.userprofile.config.UPAttribute; +import org.keycloak.representations.userprofile.config.UPConfig; import org.keycloak.testsuite.utils.io.IOUtil; +import org.keycloak.userprofile.config.UPConfigUtils; import org.keycloak.util.JsonSerialization; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Set; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertEquals; +import static org.keycloak.userprofile.DeclarativeUserProfileProvider.UP_COMPONENT_CONFIG_KEY; /** * Tests that we can import json file from previous version. MigrationTest only tests DB. @@ -37,6 +49,9 @@ public class JsonFileImport1903MigrationTest extends AbstractJsonFileImportMigra try { reps = ImportUtils.getRealmsFromStream(JsonSerialization.mapper, IOUtil.class.getResourceAsStream("/migration-test/migration-realm-19.0.3.json")); masterRep = reps.remove("master"); + + RealmRepresentation upRealm = JsonSerialization.readValue(IOUtil.class.getResourceAsStream("/migration-test/migration-realm-19.0.3-user-profile.json"), RealmRepresentation.class); + reps.put(upRealm.getRealm(), upRealm); } catch (IOException e) { throw new RuntimeException(e); } @@ -55,4 +70,24 @@ public class JsonFileImport1903MigrationTest extends AbstractJsonFileImportMigra testMigrationTo24_x(true); } + @Test + public void testUserProfileMigration() throws Exception { + List userProfileComponents = adminClient.realm("migration-user-profile") + .components() + .query(null, "org.keycloak.userprofile.UserProfileProvider"); + assertThat(userProfileComponents, hasSize(1)); + ComponentRepresentation component = userProfileComponents.get(0); + + // Test "street" attribute being presented with the expected scope selectors + UPConfig upConfig = UPConfigUtils.parseConfig(component.getConfig().getFirst(UP_COMPONENT_CONFIG_KEY)); + UPAttribute streetAttr = upConfig.getAttribute("street"); + assertThat(streetAttr, notNullValue()); + + assertThat(streetAttr.getSelector(), notNullValue()); + assertEquals(Set.of(OAuth2Constants.SCOPE_ADDRESS), streetAttr.getSelector().getScopes()); + + assertThat(streetAttr.getSelector(), notNullValue()); + assertEquals(Set.of(OAuth2Constants.SCOPE_PHONE), streetAttr.getRequired().getScopes()); + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-19.0.3-user-profile.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-19.0.3-user-profile.json new file mode 100644 index 0000000000..36f7a7290b --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-19.0.3-user-profile.json @@ -0,0 +1,26 @@ +{ + "id": "migration-user-profile", + "realm": "migration-user-profile", + "enabled": true, + "attributes" : { + "userProfileEnabled" : "true" + }, + "components" : { + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "98cef18c-bcd8-40d2-9e7d-d257298317f2", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": { + "config-piece-0": [ + "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}}},{\"name\":\"email\",\"displayName\":\"${email}\",\"permissions\":{\"edit\":[\"admin\",\"user\"],\"view\":[\"admin\",\"user\"]},\"validations\":{\"email\":{},\"length\":{\"max\":255},\"pattern\":{\"pattern\":\"[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+@example.nl\",\"error-message\":\"Invalid domain selected\"}},\"annotations\":{\"\":\"\"},\"required\":{\"roles\":[\"user\"]},\"group\":null},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}}},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}}},{\"name\":\"street\",\"displayName\":\"Street\",\"required\":{\"scopes\":[\"phone\"],\"roles\":[\"admin\",\"user\"]},\"validations\":{},\"selector\":{\"scopes\":[\"address\"]},\"permissions\":{\"view\":[\"user\"],\"edit\":[\"user\",\"admin\"]},\"annotations\":{\"foo\":\"bar\"}}]}" + ], + "config-pieces-count": [ + "1" + ] + } + } + ] + }, + "keycloakVersion" : "19.0.3" +} \ No newline at end of file