diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate4_0_0_DefaultClientScopes.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate4_0_0_DefaultClientScopes.java new file mode 100644 index 0000000000..3a53f4a5ae --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate4_0_0_DefaultClientScopes.java @@ -0,0 +1,68 @@ +/* + * Copyright 2018 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.connections.jpa.updater.liquibase.custom; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import liquibase.exception.CustomChangeException; +import liquibase.statement.core.InsertStatement; +import liquibase.structure.core.Table; + +/** + * + * @author hmlnarik + */ +public class JpaUpdate4_0_0_DefaultClientScopes extends CustomKeycloakTask { + + @Override + protected void generateStatementsImpl() throws CustomChangeException { + String clientTableName = database.correctObjectName("CLIENT", Table.class); + String clientScopeClientTableName = database.correctObjectName("CLIENT_SCOPE_CLIENT", Table.class); + + try (PreparedStatement statement = jdbcConnection.prepareStatement("SELECT ID, CLIENT_TEMPLATE_ID FROM " + clientTableName); + ResultSet rs = statement.executeQuery()) { + while (rs.next()) { + String clientId = rs.getString(1); + String clientTemplateId = rs.getString(2); + + if (clientId == null || clientId.trim().isEmpty()) { + continue; + } + if (clientTemplateId == null || clientTemplateId.trim().isEmpty()) { + continue; + } + + statements.add( + new InsertStatement(null, null, clientScopeClientTableName) + .addColumnValue("CLIENT_ID", clientId.trim()) + .addColumnValue("SCOPE_ID", clientTemplateId.trim()) + .addColumnValue("DEFAULT_SCOPE", Boolean.TRUE) + ); + } + + confirmationMessage.append("Updated " + statements.size() + " records in CLIENT_SCOPE_CLIENT table"); + } catch (Exception e) { + throw new CustomChangeException(getTaskId() + ": Exception when updating data from previous version", e); + } + } + + @Override + protected String getTaskId() { + return "Update 4.0.0.Final (Default client scopes)"; + } + +} diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-4.0.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-4.0.0.xml index e34f2d3f49..727852f974 100644 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-4.0.0.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-4.0.0.xml @@ -85,7 +85,12 @@ - + + + + + + @@ -147,15 +152,6 @@ constraintName="FK_CL_SCOPE_ATTR_SCOPE" referencedTableName="CLIENT_SCOPE" referencedColumnNames="ID" /> - - - - - - - - - @@ -171,6 +167,16 @@ + + + + + + + + + + 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 eb27600cad..95c81201b8 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 @@ -66,10 +66,13 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; -import org.keycloak.common.Profile; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import static org.junit.Assert.assertThat; /** * @@ -635,21 +638,39 @@ public class ExportImportUtil { Assert.assertTrue(!source.stream().filter(object -> !predicate.stream().filter(predicate1 -> predicate1.test(object)).findFirst().isPresent()).findAny().isPresent()); } + private static Matcher> getDefaultClientScopeNameMatcher(ClientRepresentation rep) { + switch (rep.getClientId()) { + case "client-with-template": + return Matchers.hasItem("Default_test_template"); + default: + return Matchers.not(Matchers.hasItem("Default_test_template")); + } + } + + public static void testClientDefaultClientScopes(RealmResource realm) { + for (ClientRepresentation rep : realm.clients().findAll(true)) { + Matcher> expectedDefaultClientScopeNames = getDefaultClientScopeNameMatcher(rep); + + assertThat("Default client scopes for " + rep.getClientId(), rep.getDefaultClientScopes(), expectedDefaultClientScopeNames); + } + } public static void testRealmDefaultClientScopes(RealmResource realm) { // Assert built-in scopes were created in realm List clientScopes = realm.clientScopes().findAll(); - Map clientScopesMap = clientScopes - .stream().collect(Collectors.toMap(clientScope -> clientScope.getName(), clientScope -> clientScope)); + Map clientScopesMap = clientScopes.stream() + .collect(Collectors.toMap(ClientScopeRepresentation::getName, Function.identity())); - org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(OAuth2Constants.SCOPE_PROFILE)); - org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(OAuth2Constants.SCOPE_EMAIL)); - org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(OAuth2Constants.SCOPE_ADDRESS)); - org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(OAuth2Constants.SCOPE_PHONE)); - org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(OAuth2Constants.OFFLINE_ACCESS)); - org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(OIDCLoginProtocolFactory.ROLES_SCOPE)); - org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE)); - org.keycloak.testsuite.Assert.assertTrue(clientScopesMap.containsKey(SamlProtocolFactory.SCOPE_ROLE_LIST)); + assertThat(clientScopesMap.keySet(), Matchers.hasItems( + OAuth2Constants.SCOPE_PROFILE, + OAuth2Constants.SCOPE_EMAIL, + OAuth2Constants.SCOPE_ADDRESS, + OAuth2Constants.SCOPE_PHONE, + OAuth2Constants.OFFLINE_ACCESS, + OIDCLoginProtocolFactory.ROLES_SCOPE, + OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE, + SamlProtocolFactory.SCOPE_ROLE_LIST + )); // Check content of some client scopes Map protocolMappers = clientScopesMap.get(OAuth2Constants.SCOPE_EMAIL).getProtocolMappers() @@ -662,17 +683,21 @@ public class ExportImportUtil { org.keycloak.testsuite.Assert.assertNames(offlineRoleScopes, OAuth2Constants.OFFLINE_ACCESS); // Check default client scopes and optional client scopes expected - Map defaultClientScopes = realm.getDefaultDefaultClientScopes() - .stream().collect(Collectors.toMap(clientScope -> clientScope.getName(), clientScope -> clientScope)); - org.keycloak.testsuite.Assert.assertTrue(defaultClientScopes.containsKey(OAuth2Constants.SCOPE_PROFILE)); - org.keycloak.testsuite.Assert.assertTrue(defaultClientScopes.containsKey(OAuth2Constants.SCOPE_EMAIL)); - org.keycloak.testsuite.Assert.assertTrue(defaultClientScopes.containsKey(OIDCLoginProtocolFactory.ROLES_SCOPE)); - org.keycloak.testsuite.Assert.assertTrue(defaultClientScopes.containsKey(OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE)); + Set defaultClientScopes = realm.getDefaultDefaultClientScopes() + .stream().map(ClientScopeRepresentation::getName).collect(Collectors.toSet()); + assertThat(defaultClientScopes, Matchers.hasItems( + OAuth2Constants.SCOPE_PROFILE, + OAuth2Constants.SCOPE_EMAIL, + OIDCLoginProtocolFactory.ROLES_SCOPE, + OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE + )); - Map optionalClientScopes = realm.getDefaultOptionalClientScopes() - .stream().collect(Collectors.toMap(clientScope -> clientScope.getName(), clientScope -> clientScope)); - org.keycloak.testsuite.Assert.assertTrue(optionalClientScopes.containsKey(OAuth2Constants.SCOPE_ADDRESS)); - org.keycloak.testsuite.Assert.assertTrue(optionalClientScopes.containsKey(OAuth2Constants.SCOPE_PHONE)); - org.keycloak.testsuite.Assert.assertTrue(optionalClientScopes.containsKey(OAuth2Constants.OFFLINE_ACCESS)); + Set optionalClientScopes = realm.getDefaultOptionalClientScopes() + .stream().map(ClientScopeRepresentation::getName).collect(Collectors.toSet()); + assertThat(optionalClientScopes, Matchers.hasItems( + OAuth2Constants.SCOPE_ADDRESS, + OAuth2Constants.SCOPE_PHONE, + OAuth2Constants.OFFLINE_ACCESS + )); } } 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 0b0b99da5c..a8376830ac 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 @@ -106,6 +106,7 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { if (supportsAuthzService) { expectedClientIds.add("authz-servlet"); + expectedClientIds.add("client-with-template"); } assertNames(migrationRealm.clients().findAll(), expectedClientIds.toArray(new String[expectedClientIds.size()])); @@ -211,6 +212,7 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { protected void testMigrationTo4_0_0() { testRealmDefaultClientScopes(this.masterRealm); testRealmDefaultClientScopes(this.migrationRealm); + testClientDefaultClientScopes(this.migrationRealm); testOfflineScopeAddedToClient(); } @@ -493,6 +495,11 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { ExportImportUtil.testRealmDefaultClientScopes(realm); } + private void testClientDefaultClientScopes(RealmResource realm) { + log.info("Testing default client scopes transferred from client scope in realm: " + realm.toRepresentation().getRealm()); + ExportImportUtil.testClientDefaultClientScopes(realm); + } + private void testOfflineScopeAddedToClient() { log.infof("Testing offline_access optional scope present in realm %s for client migration-test-client", migrationRealm.toRepresentation().getRealm()); 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 61afd71b45..0b2b99b163 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 @@ -25,7 +25,6 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.arquillian.DeploymentTargetModifier; import org.keycloak.testsuite.runonserver.RunOnServerDeployment; import org.keycloak.testsuite.utils.io.IOUtil; -import org.keycloak.testsuite.util.WaitUtils; import org.keycloak.util.JsonSerialization; import java.io.IOException; diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-2.5.5.Final.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-2.5.5.Final.json index e67ee6ad8f..bf3469676e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-2.5.5.Final.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-2.5.5.Final.json @@ -2108,7 +2108,15 @@ }, { "client" : "security-admin-console", "roles" : [ "realm-admin" ] - } ] + } ], + "migration-test-client": [ + { + "clientTemplate": "Default test template", + "roles": [ + "migration-test-client-role" + ] + } + ] }, "clients" : [ { "id" : "6f27b0c3-9fc0-4e04-b69a-2031349acf04", @@ -2802,6 +2810,33 @@ "useTemplateScope" : false, "useTemplateMappers" : false }, + { + "id": "26519045-3cd4-4f2a-974b-e1447907834a", + "clientId": "client-with-template", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": -1, + "clientTemplate": "Default test template", + "useTemplateConfig": false, + "useTemplateScope": true, + "useTemplateMappers": true + }, { "id": "70e8e897-82d4-49ab-82c9-c37e1a48b6bb", "clientId": "authz-servlet", @@ -2858,7 +2893,15 @@ ] } }], - "clientTemplates" : [ ], + "clientTemplates": [ + { + "id": "d43ae38d-80a1-471a-9a80-5f9a0d34d7a4", + "name": "Default test template", + "description": "Test client template", + "protocol": "openid-connect", + "fullScopeAllowed": false + } + ], "browserSecurityHeaders" : { "xContentTypeOptions" : "nosniff", "xFrameOptions" : "SAMEORIGIN", diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-3.4.3.Final.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-3.4.3.Final.json index c104dbbe11..87e6073b19 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-3.4.3.Final.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-3.4.3.Final.json @@ -313,6 +313,16 @@ "notBefore" : 0, "groups" : [ ] } ], + "clientScopeMappings" : { + "migration-test-client": [ + { + "clientTemplate": "Default test template", + "roles": [ + "migration-test-client-role" + ] + } + ] + }, "clients" : [ { "id" : "6b9ba4ca-fb7c-4e17-a3e6-88f3a17397cc", "clientId" : "account", @@ -1010,6 +1020,33 @@ "useTemplateScope" : false, "useTemplateMappers" : false }, + { + "id": "26519045-3cd4-4f2a-974b-e1447907834a", + "clientId": "client-with-template", + "surrogateAuthRequired": false, + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": -1, + "clientTemplate": "Default test template", + "useTemplateConfig": false, + "useTemplateScope": true, + "useTemplateMappers": true + }, { "id": "70e8e897-82d4-49ab-82c9-c37e1a48b6bb", "clientId": "authz-servlet", @@ -1066,7 +1103,15 @@ ] } }], - "clientTemplates" : [ ], + "clientTemplates": [ + { + "id": "d43ae38d-80a1-471a-9a80-5f9a0d34d7a4", + "name": "Default test template", + "description": "Test client template", + "protocol": "openid-connect", + "fullScopeAllowed": false + } + ], "browserSecurityHeaders" : { "xContentTypeOptions" : "nosniff", "xRobotsTag" : "none",