From f1acdc000e4e3a1f0fc092432d17b47c23b07d77 Mon Sep 17 00:00:00 2001 From: Stefan Guilhen Date: Fri, 3 May 2019 14:32:21 -0300 Subject: [PATCH] [KEYCLOAK-10168] Handle microprofile-jwt client scope migration --- .../migration/MigrationModelManager.java | 4 +- .../keycloak/migration/MigrationProvider.java | 8 ++ .../migration/migrators/MigrateTo6_0_0.java | 74 +++++++++++++++++++ .../oidc/OIDCLoginProtocolFactory.java | 35 ++++++--- .../migration/DefaultMigrationProvider.java | 4 + .../exportimport/ExportImportUtil.java | 4 +- .../migration/AbstractMigrationTest.java | 28 +++++++ .../JsonFileImport198MigrationTest.java | 1 + .../JsonFileImport255MigrationTest.java | 1 + .../JsonFileImport343MigrationTest.java | 1 + .../JsonFileImport483MigrationTest.java | 1 + .../testsuite/migration/MigrationTest.java | 4 + 12 files changed, 154 insertions(+), 11 deletions(-) create mode 100644 server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo6_0_0.java diff --git a/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java b/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java index 10ddab4aec..96a57500cc 100755 --- a/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java +++ b/server-spi-private/src/main/java/org/keycloak/migration/MigrationModelManager.java @@ -41,6 +41,7 @@ import org.keycloak.migration.migrators.MigrateTo3_4_2; import org.keycloak.migration.migrators.MigrateTo4_0_0; import org.keycloak.migration.migrators.MigrateTo4_2_0; import org.keycloak.migration.migrators.MigrateTo4_6_0; +import org.keycloak.migration.migrators.MigrateTo6_0_0; import org.keycloak.migration.migrators.Migration; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -76,7 +77,8 @@ public class MigrationModelManager { new MigrateTo3_4_2(), new MigrateTo4_0_0(), new MigrateTo4_2_0(), - new MigrateTo4_6_0() + new MigrateTo4_6_0(), + new MigrateTo6_0_0() }; public static void migrate(KeycloakSession session) { diff --git a/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java b/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java index 3cb7e797ad..98f8840738 100755 --- a/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/migration/MigrationProvider.java @@ -61,4 +61,12 @@ public interface MigrationProvider extends Provider { */ ClientScopeModel addOIDCWebOriginsClientScope(RealmModel realm); + /** + * Adds the {@code microprofile-jwt} optional client scope to the realm and returns the created scope. If the scope + * already exists in the realm then the existing scope is returned. + * + * @param realm the realm to which the scope is to be added. + * @return a reference to the {@code microprofile-jwt} client scope that was either created or already exists in the realm. + */ + ClientScopeModel addOIDCMicroprofileJWTClientScope(RealmModel realm); } diff --git a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo6_0_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo6_0_0.java new file mode 100644 index 0000000000..6abfbed1fb --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo6_0_0.java @@ -0,0 +1,74 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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. + */ + +package org.keycloak.migration.migrators; + +import org.jboss.logging.Logger; +import org.keycloak.migration.MigrationProvider; +import org.keycloak.migration.ModelVersion; +import org.keycloak.models.ClientModel; +import org.keycloak.models.ClientScopeModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.representations.idm.RealmRepresentation; + +/** + * Implements the migration necessary for version 6.0.0. + * + * @author Stefan Guilhen + */ +public class MigrateTo6_0_0 implements Migration { + + public static final ModelVersion VERSION = new ModelVersion("6.0.0"); + + private static final Logger LOG = Logger.getLogger(MigrateTo6_0_0.class); + + @Override + public ModelVersion getVersion() { + return VERSION; + } + + @Override + public void migrate(KeycloakSession session) { + session.realms().getRealms().stream().forEach(r -> { + migrateRealm(session, r, false); + }); + } + + @Override + public void migrateImport(KeycloakSession session, RealmModel realm, RealmRepresentation rep, boolean skipUserDependent) { + migrateRealm(session, realm, true); + } + + protected void migrateRealm(KeycloakSession session, RealmModel realm, boolean jsn) { + MigrationProvider migrationProvider = session.getProvider(MigrationProvider.class); + + // create 'microprofile-jwt' optional client scope in the realm. + ClientScopeModel mpJWTScope = migrationProvider.addOIDCMicroprofileJWTClientScope(realm); + + LOG.debugf("Added '%s' optional client scope", mpJWTScope.getName()); + + // assign 'microprofile-jwt' optional client scope to all the OIDC clients. + for (ClientModel client : realm.getClients()) { + if ((client.getProtocol() == null || "openid-connect".equals(client.getProtocol())) && (!client.isBearerOnly())) { + client.addClientScope(mpJWTScope, false); + } + } + + LOG.debugf("Client scope '%s' assigned to all the clients", mpJWTScope.getName()); + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java index 6c3fce9a90..efa4caa227 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java @@ -267,15 +267,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { addRolesClientScope(newRealm); addWebOriginsClientScope(newRealm); - - ClientScopeModel microprofileScope = newRealm.addClientScope(MICROPROFILE_JWT_SCOPE); - microprofileScope.setDescription("Microprofile - JWT built-in scope"); - microprofileScope.setDisplayOnConsentScreen(false); - microprofileScope.setIncludeInTokenScope(true); - microprofileScope.setProtocol(getId()); - microprofileScope.addProtocolMapper(builtins.get(UPN)); - microprofileScope.addProtocolMapper(builtins.get(GROUPS)); - newRealm.addDefaultClientScope(microprofileScope, false); + addMicroprofileJWTClientScope(newRealm); } @@ -322,6 +314,31 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { return originsScope; } + /** + * Adds the {@code microprofile-jwt} optional client scope to the specified realm. If a {@code microprofile-jwt} client scope + * already exists in the realm then the existing scope is returned. Otherwise, a new scope is created and returned. + * + * @param newRealm the realm to which the {@code microprofile-jwt} scope is to be added. + * @return a reference to the {@code microprofile-jwt} client scope that was either created or already exists in the realm. + */ + public static ClientScopeModel addMicroprofileJWTClientScope(RealmModel newRealm) { + ClientScopeModel microprofileScope = KeycloakModelUtils.getClientScopeByName(newRealm, MICROPROFILE_JWT_SCOPE); + if (microprofileScope == null) { + microprofileScope = newRealm.addClientScope(MICROPROFILE_JWT_SCOPE); + microprofileScope.setDescription("Microprofile - JWT built-in scope"); + microprofileScope.setDisplayOnConsentScreen(false); + microprofileScope.setIncludeInTokenScope(true); + microprofileScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + microprofileScope.addProtocolMapper(builtins.get(UPN)); + microprofileScope.addProtocolMapper(builtins.get(GROUPS)); + newRealm.addDefaultClientScope(microprofileScope, false); + } else { + logger.debugf("Client scope '%s' already exists in realm '%s'. Skip creating it.", MICROPROFILE_JWT_SCOPE, newRealm.getName()); + } + + return microprofileScope; + } + @Override protected void addDefaults(ClientModel client) { } diff --git a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java index 0c2e5f58fe..619fbdecf7 100755 --- a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java +++ b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java @@ -96,6 +96,10 @@ public class DefaultMigrationProvider implements MigrationProvider { return OIDCLoginProtocolFactory.addWebOriginsClientScope(realm); } + @Override + public ClientScopeModel addOIDCMicroprofileJWTClientScope(RealmModel realm) { + return OIDCLoginProtocolFactory.addMicroprofileJWTClientScope(realm); + } @Override public void close() { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java index 5f10066e17..2829faedf0 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java @@ -680,6 +680,7 @@ public class ExportImportUtil { OAuth2Constants.OFFLINE_ACCESS, OIDCLoginProtocolFactory.ROLES_SCOPE, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE, + OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE, SamlProtocolFactory.SCOPE_ROLE_LIST )); @@ -708,7 +709,8 @@ public class ExportImportUtil { assertThat(optionalClientScopes, Matchers.hasItems( OAuth2Constants.SCOPE_ADDRESS, OAuth2Constants.SCOPE_PHONE, - OAuth2Constants.OFFLINE_ACCESS + OAuth2Constants.OFFLINE_ACCESS, + OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE )); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java index 939a141da2..845c49cf58 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java @@ -232,6 +232,13 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { testRolesAndWebOriginsScopesAddedToClient(); } + protected void testMigrationTo6_0_0() { + // check that all expected scopes exist in the migrated realm. + testRealmDefaultClientScopes(migrationRealm); + // check that the 'microprofile-jwt' scope was added to the migrated clients. + testMicroprofileJWTScopeAddedToClient(); + } + private void testGroupPolicyTypeFineGrainedAdminPermission() { ClientsResource clients = migrationRealm.clients(); ClientRepresentation clientRepresentation = clients.findByClientId("realm-management").get(0); @@ -519,6 +526,23 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { } + /** + * Checks if the {@code microprofile-jwt} optional client scope has been added to the clients. + */ + private void testMicroprofileJWTScopeAddedToClient() { + log.infof("Testing microprofile-jwt optional scope present in realm %s for client migration-test-client", migrationRealm.toRepresentation().getRealm()); + + List optionalClientScopes = ApiUtil.findClientByClientId(this.migrationRealm, "migration-test-client").getOptionalClientScopes(); + + Set defaultClientScopeNames = optionalClientScopes.stream() + .map(ClientScopeRepresentation::getName) + .collect(Collectors.toSet()); + + if (!defaultClientScopeNames.contains(OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE)) { + Assert.fail("Client scope 'microprofile-jwt' not found as optional scope of client migration-test-client"); + } + } + private void testRequiredActionsPriority(RealmResource... realms) { log.info("testing required action's priority"); for (RealmResource realm : realms) { @@ -572,4 +596,8 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { protected void testMigrationTo5_x() { // so far nothing } + + protected void testMigrationTo6_x() { + testMigrationTo6_0_0(); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java index f4b29fd62c..3e9a6890db 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java @@ -74,6 +74,7 @@ public class JsonFileImport198MigrationTest extends AbstractJsonFileImportMigrat testMigrationTo3_x(); testMigrationTo4_x(false, false); testMigrationTo5_x(); + testMigrationTo6_x(); } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java index 23e1a45176..13de40d52f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java @@ -67,6 +67,7 @@ public class JsonFileImport255MigrationTest extends AbstractJsonFileImportMigrat testMigrationTo3_x(); testMigrationTo4_x(true, false); testMigrationTo5_x(); + testMigrationTo6_x(); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java index 2d0fc5149b..1d6cbcf4be 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java @@ -66,6 +66,7 @@ public class JsonFileImport343MigrationTest extends AbstractJsonFileImportMigrat checkRealmsImported(); testMigrationTo4_x(true, false); testMigrationTo5_x(); + testMigrationTo6_x(); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport483MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport483MigrationTest.java index bb99ee7377..8a4f50c23c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport483MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport483MigrationTest.java @@ -60,6 +60,7 @@ public class JsonFileImport483MigrationTest extends AbstractJsonFileImportMigrat public void migration4_8_3Test() throws Exception { checkRealmsImported(); testMigrationTo5_x(); + testMigrationTo6_x(); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java index 871150f9ef..b6f63c3456 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java @@ -70,6 +70,7 @@ public class MigrationTest extends AbstractMigrationTest { public void migration4_xTest() { testMigratedData(); testMigrationTo5_x(); + testMigrationTo6_x(); } @Test @@ -78,6 +79,7 @@ public class MigrationTest extends AbstractMigrationTest { testMigratedData(); testMigrationTo4_x(); testMigrationTo5_x(); + testMigrationTo6_x(); } @Test @@ -87,6 +89,7 @@ public class MigrationTest extends AbstractMigrationTest { testMigrationTo3_x(); testMigrationTo4_x(); testMigrationTo5_x(); + testMigrationTo6_x(); } @Test @@ -97,6 +100,7 @@ public class MigrationTest extends AbstractMigrationTest { testMigrationTo3_x(); testMigrationTo4_x(false, false); testMigrationTo5_x(); + testMigrationTo6_x(); } }