From 717f65621e479b359096f7e966d90cb38a35d45a Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Mon, 7 Nov 2022 15:06:46 +0100 Subject: [PATCH 01/21] Allow a partial import to overwrite the default role (#15316) Closes #9891 --- .../models/cache/infinispan/RealmAdapter.java | 1 + .../partialimport/RolesPartialImport.java | 18 +++++++ .../partialimport/PartialImportTest.java | 51 ++++++++++++------- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index 038178482180..4337ef91159a 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -1051,6 +1051,7 @@ public void setDefaultRole(RoleModel role) { @Override public RoleModel getDefaultRole() { + if (isUpdated()) return updated.getDefaultRole(); return cached.getDefaultRoleId() == null ? null : cacheSession.getRoleById(this, cached.getDefaultRoleId()); } diff --git a/services/src/main/java/org/keycloak/partialimport/RolesPartialImport.java b/services/src/main/java/org/keycloak/partialimport/RolesPartialImport.java index c4b295bfb59e..58de4cab70e4 100644 --- a/services/src/main/java/org/keycloak/partialimport/RolesPartialImport.java +++ b/services/src/main/java/org/keycloak/partialimport/RolesPartialImport.java @@ -18,6 +18,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.PartialImportRepresentation; @@ -29,6 +30,7 @@ import javax.ws.rs.core.Response; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -54,6 +56,7 @@ public class RolesPartialImport implements PartialImport { private final RealmRolesPartialImport realmRolesPI = new RealmRolesPartialImport(); private final ClientRolesPartialImport clientRolesPI = new ClientRolesPartialImport(); + private RoleRepresentation newDefaultRole; @Override public void prepare(PartialImportRepresentation rep, RealmModel realm, KeycloakSession session) throws ErrorResponseException { @@ -66,6 +69,16 @@ private void prepareRealmRoles(PartialImportRepresentation rep, RealmModel realm realmRolesPI.prepare(rep, realm, session); this.realmRolesToOverwrite = realmRolesPI.getToOverwrite(); + if (realmRolesToOverwrite.size() > 0) { + String defaultRoleName = realm.getDefaultRole().getName(); + for (RoleRepresentation representation : realmRolesToOverwrite) { + if (Objects.equals(defaultRoleName, representation.getName())) { + this.newDefaultRole = representation; + break; + } + } + } + this.realmRolesToSkip = realmRolesPI.getToSkip(); } @@ -94,6 +107,11 @@ public PartialImportResults doImport(PartialImportRepresentation rep, RealmModel if (rep.hasRealmRoles()) setUniqueIds(rep.getRoles().getRealm()); if (rep.hasClientRoles()) setUniqueIds(rep.getRoles().getClient()); + if (newDefaultRole != null) { + RoleModel defaultRole = RepresentationToModel.createRole(realm, newDefaultRole); + realm.setDefaultRole(defaultRole); + } + try { RepresentationToModel.importRoles(rep.getRoles(), realm); } catch (Exception e) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java index 0a511989ea5a..f6896be62a41 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java @@ -49,6 +49,7 @@ import javax.ws.rs.core.Response; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -268,7 +269,7 @@ private void addGroups() { piRep.setGroups(groups); } - private void addClients(boolean withServiceAccounts) throws IOException { + private void addClients(boolean withServiceAccounts) { List clients = new ArrayList<>(); List serviceAccounts = new ArrayList<>(); @@ -440,7 +441,7 @@ public void testAddUsersWithDuplicateEmailsAllowed() { doImport(); UserRepresentation user = createUserRepresentation(USER_PREFIX + 999, USER_PREFIX + 1 + "@foo.com", "foo", "bar", true); - piRep.setUsers(Arrays.asList(user)); + piRep.setUsers(List.of(user)); PartialImportResults results = doImport(); assertEquals(1, results.getAdded()); @@ -479,7 +480,7 @@ public void testAddUsersWithTermsAndConditions() { } @Test - public void testAddClients() throws IOException { + public void testAddClients() { setFail(); addClients(false); @@ -495,7 +496,7 @@ public void testAddClients() throws IOException { } @Test - public void testAddClientsWithServiceAccountsAndAuthorization() throws IOException { + public void testAddClientsWithServiceAccountsAndAuthorization() { setFail(); addClients(true); @@ -621,7 +622,7 @@ public void testAddGroupsFail() { } @Test - public void testAddClientsFail() throws IOException { + public void testAddClientsFail() { addClients(false); testFail(); } @@ -676,13 +677,13 @@ public void testAddGroupsSkip() { } @Test - public void testAddClientsSkip() throws IOException { + public void testAddClientsSkip() { addClients(false); testSkip(); } @Test - public void testAddClientsSkipWithServiceAccountsAndAuthorization() throws IOException { + public void testAddClientsSkipWithServiceAccountsAndAuthorization() { addClients(true); setSkip(); PartialImportResults results = doImport(); @@ -742,13 +743,13 @@ public void testAddGroupsOverwrite() { } @Test - public void testAddClientsOverwrite() throws IOException { + public void testAddClientsOverwrite() { addClients(false); testOverwrite(); } @Test - public void testAddClientsOverwriteWithServiceAccountsAndAuthorization() throws IOException { + public void testAddClientsOverwriteWithServiceAccountsAndAuthorization() { addClients(true); setOverwrite(); PartialImportResults results = doImport(); @@ -759,7 +760,7 @@ public void testAddClientsOverwriteWithServiceAccountsAndAuthorization() throws } @Test - public void testAddClientsOverwriteServiceAccountsWithNoServiceAccounts() throws IOException { + public void testAddClientsOverwriteServiceAccountsWithNoServiceAccounts() { addClients(true); setOverwrite(); PartialImportResults results = doImport(); @@ -806,7 +807,7 @@ public void testAddClientRolesOverwrite() { testOverwrite(); } - private void importEverything(boolean withServiceAccounts) throws IOException { + private void importEverything(boolean withServiceAccounts) { addUsers(); addGroups(); addClients(withServiceAccounts); @@ -824,7 +825,7 @@ private void importEverything(boolean withServiceAccounts) throws IOException { } @Test - public void testEverythingFail() throws IOException { + public void testEverythingFail() { setFail(); importEverything(false); PartialImportResults results = doImport(); // second import will fail because not allowed to skip or overwrite @@ -832,7 +833,7 @@ public void testEverythingFail() throws IOException { } @Test - public void testEverythingSkip() throws IOException { + public void testEverythingSkip() { setSkip(); importEverything(false); PartialImportResults results = doImport(); @@ -840,7 +841,7 @@ public void testEverythingSkip() throws IOException { } @Test - public void testEverythingSkipWithServiceAccounts() throws IOException { + public void testEverythingSkipWithServiceAccounts() { setSkip(); importEverything(true); PartialImportResults results = doImport(); @@ -848,7 +849,7 @@ public void testEverythingSkipWithServiceAccounts() throws IOException { } @Test - public void testEverythingOverwrite() throws IOException { + public void testEverythingOverwrite() { setOverwrite(); importEverything(false); PartialImportResults results = doImport(); @@ -856,7 +857,7 @@ public void testEverythingOverwrite() throws IOException { } @Test - public void testEverythingOverwriteWithServiceAccounts() throws IOException { + public void testEverythingOverwriteWithServiceAccounts() { setOverwrite(); importEverything(true); PartialImportResults results = doImport(); @@ -877,7 +878,7 @@ public void testOverwriteExistingClientWithRoles() { RolesRepresentation roles = new RolesRepresentation(); roles.setClient(clients); - piRep.setClients(Arrays.asList(client)); + piRep.setClients(List.of(client)); piRep.setRoles(roles); doImport(); @@ -898,7 +899,7 @@ public void testOverwriteExistingInternalClient() { @Test public void testOverwriteExistingClientWithServiceAccount() { setOverwrite(); - piRep.setClients(Arrays.asList(testRealmResource().clients().findByClientId(CLIENT_SERVICE_ACCOUNT).get(0))); + piRep.setClients(Collections.singletonList(testRealmResource().clients().findByClientId(CLIENT_SERVICE_ACCOUNT).get(0))); Assert.assertEquals(1, doImport().getOverwritten()); @@ -906,4 +907,18 @@ public void testOverwriteExistingClientWithServiceAccount() { testRealmResource().clients().get(client.getId()).getServiceAccountUser(); } + @Test + public void testOverwriteDefaultRole() { + setOverwrite(); + + RolesRepresentation roles = new RolesRepresentation(); + RoleRepresentation oldDefaultRole = testRealmResource().toRepresentation().getDefaultRole(); + roles.setRealm(Collections.singletonList(oldDefaultRole)); + piRep.setRoles(roles); + + Assert.assertEquals("default role should have been overwritten", 1, doImport().getOverwritten()); + Assert.assertNotEquals("when overwriting, the ID of the role changes", + testRealmResource().toRepresentation().getDefaultRole().getId(), oldDefaultRole.getId()); + } + } From 6bab737e85a5bb71893b6fa7007e2feb6c679a7f Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 7 Nov 2022 10:52:02 -0500 Subject: [PATCH 02/21] Fix classpath separator for windows startup script (#15300) (#15377) Closes #15281 Co-authored-by: Stian Thorgersen Co-authored-by: Fouad Almalki --- quarkus/dist/src/main/content/bin/kc.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quarkus/dist/src/main/content/bin/kc.bat b/quarkus/dist/src/main/content/bin/kc.bat index 4fe25e31db2c..241fe11864e4 100644 --- a/quarkus/dist/src/main/content/bin/kc.bat +++ b/quarkus/dist/src/main/content/bin/kc.bat @@ -124,7 +124,7 @@ if "x%JAVA_HOME%" == "x" ( ) ) -set "CLASSPATH_OPTS=%DIRNAME%..\lib\quarkus-run.jar:%DIRNAME%..\lib\bootstrap\*" +set "CLASSPATH_OPTS=%DIRNAME%..\lib\quarkus-run.jar;%DIRNAME%..\lib\bootstrap\*" set "JAVA_RUN_OPTS=%JAVA_OPTS% -Dkc.home.dir="%DIRNAME%.." -Djboss.server.config.dir="%DIRNAME%..\conf" -Dkeycloak.theme.dir="%DIRNAME%..\themes" %SERVER_OPTS% -cp "%CLASSPATH_OPTS%" io.quarkus.bootstrap.runner.QuarkusEntryPoint %CONFIG_ARGS%" From 5843c811bf7a5e6631fead09b3e2d1e615e5c69c Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Mon, 7 Nov 2022 19:18:04 +0100 Subject: [PATCH 03/21] Use RESOURCE_LOCAL transactions for JPA map storage (#15315) Closes #15248 --- .../quarkus/deployment/KeycloakProcessor.java | 24 ++++++++++++++++++- .../QuarkusJpaMapStorageProviderFactory.java | 12 ++-------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java index 1c3f884300a4..92ec803e6e57 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java @@ -20,6 +20,7 @@ import static org.keycloak.quarkus.runtime.KeycloakRecorder.DEFAULT_HEALTH_ENDPOINT; import static org.keycloak.quarkus.runtime.KeycloakRecorder.DEFAULT_METRICS_ENDPOINT; import static org.keycloak.quarkus.runtime.Providers.getProviderManager; +import static org.keycloak.quarkus.runtime.configuration.Configuration.getConfig; import static org.keycloak.quarkus.runtime.configuration.Configuration.getPropertyNames; import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_QUARKUS; import static org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource.QUARKUS_PROPERTY_ENABLED; @@ -49,6 +50,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.function.Consumer; @@ -81,6 +83,7 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; import org.hibernate.jpa.boot.internal.PersistenceXmlParser; +import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.ClassInfo; @@ -93,6 +96,7 @@ import org.keycloak.common.crypto.FipsMode; import org.keycloak.config.SecurityOptions; import org.keycloak.config.StorageOptions; +import org.keycloak.config.TransactionOptions; import org.keycloak.connections.jpa.JpaConnectionProvider; import org.keycloak.connections.jpa.JpaConnectionSpi; import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory; @@ -273,7 +277,25 @@ private void configureDefaultPersistenceUnitProperties(ParsedPersistenceXmlDescr Properties unitProperties = descriptor.getProperties(); unitProperties.setProperty(AvailableSettings.DIALECT, config.defaultPersistenceUnit.dialect.dialect.orElse(null)); - unitProperties.setProperty(AvailableSettings.JPA_TRANSACTION_TYPE, PersistenceUnitTransactionType.JTA.name()); + if (Objects.equals(getConfig().getConfigValue("kc.transaction-jta-enabled").getValue(), "disabled")) { + unitProperties.setProperty(AvailableSettings.JPA_TRANSACTION_TYPE, PersistenceUnitTransactionType.RESOURCE_LOCAL.name()); + + // Only changing this for the new map storage to keep the legacy JPA store untouched as it wasn't tested for the legacy store. + // follow-up on this in https://github.com/keycloak/keycloak/issues/13222 to re-visit the auto-commit handling + String storage = Configuration.getRawValue( + MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX.concat(StorageOptions.STORAGE.getKey())); + if (storage != null) { + // Needed to change the connection handling to avoid Hibernate returning the connection too early, + // which then interfered with the auto-commit reset that's done at the end of the transaction and PgConnection throwing a "Cannot commit when autoCommit is enabled." + // The current default is DELAYED_ACQUISITION_AND_RELEASE_BEFORE_TRANSACTION_COMPLETION which would only make sense for JTA IMHO. + // https://github.com/quarkusio/quarkus/blob/8d89101ffa65465b33d06360047095046bb726e4/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java#L287-L288 + unitProperties.setProperty(AvailableSettings.CONNECTION_HANDLING, PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION.name()); + } + } else { + // will happen for both "enabled" and "xa" + unitProperties.setProperty(AvailableSettings.JPA_TRANSACTION_TYPE, PersistenceUnitTransactionType.JTA.name()); + } + unitProperties.setProperty(AvailableSettings.QUERY_STARTUP_CHECKING, Boolean.FALSE.toString()); String dbKind = jdbcDataSources.get(0).getDbKind(); diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/database/jpa/QuarkusJpaMapStorageProviderFactory.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/database/jpa/QuarkusJpaMapStorageProviderFactory.java index db106292b76b..c10545016d00 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/database/jpa/QuarkusJpaMapStorageProviderFactory.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/database/jpa/QuarkusJpaMapStorageProviderFactory.java @@ -17,10 +17,6 @@ package org.keycloak.quarkus.runtime.storage.database.jpa; -import static org.keycloak.config.StorageOptions.STORAGE; -import static org.keycloak.quarkus.runtime.configuration.Configuration.getOptionalValue; -import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX; - import java.lang.annotation.Annotation; import java.sql.Connection; import java.sql.SQLException; @@ -31,7 +27,6 @@ import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.internal.SessionImpl; import org.keycloak.config.StorageOptions; -import org.keycloak.models.ModelException; import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory; import io.quarkus.arc.Arc; @@ -58,8 +53,7 @@ protected EntityManagerFactory createEntityManagerFactory() { @Override protected EntityManager getEntityManager() { EntityManager em = super.getEntityManager(); - try { - Connection connection = em.unwrap(SessionImpl.class).connection(); + em.unwrap(SessionImpl.class).doWork(connection -> { // In the Undertow setup, Hibernate sets the connection to non-autocommit, and in the Quarkus setup the XA transaction manager does this. // For the Quarkus setup without a XA transaction manager, we didn't find a way to have this setup automatically. // There is also no known option to configure this in the Agroal DB connection pool in a Quarkus setup: @@ -70,9 +64,7 @@ protected EntityManager getEntityManager() { if (connection.getAutoCommit()) { connection.setAutoCommit(false); } - } catch (SQLException e) { - throw new ModelException("unable to set non-auto-commit to false"); - } + }); return em; } From 1ce61943a6570815fd1982a6ec35be83ae4ef5a9 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 7 Nov 2022 14:45:04 -0500 Subject: [PATCH 04/21] Upgrade Bouncycastle from 1.68 to 1.70 (#14198) (#15379) * Upgrade Bouncycastle to 1.70 * Update pom.xml * Update pom.xml Co-authored-by: Stian Thorgersen Co-authored-by: Stian Thorgersen Co-authored-by: Brendon Vicente <48719973+Bredstone@users.noreply.github.com> --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 86382423d90a..0813862e569e 100644 --- a/pom.xml +++ b/pom.xml @@ -65,12 +65,12 @@ 4.4.14 0.6 1.5.1.Final - 1.68 + 1.70 1.0.2.3 - 1.0.5 - 1.0.12.2 + 1.0.7 + 1.0.14 3.3.10 3.3.10 From 37e72068daee535e2bc1e2e09fafbc9adb34808d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Muzik=C3=A1=C5=99?= Date: Fri, 4 Nov 2022 18:15:07 +0100 Subject: [PATCH 05/21] Fix race condition while updating Secrets labels in Operator --- .../controllers/WatchedSecretsStore.java | 22 +++++++++++-------- .../integration/BaseOperatorTest.java | 8 +++++-- .../integration/RealmImportTest.java | 5 +++-- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/operator/src/main/java/org/keycloak/operator/controllers/WatchedSecretsStore.java b/operator/src/main/java/org/keycloak/operator/controllers/WatchedSecretsStore.java index daeb09140a4c..8a4a63110c71 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/WatchedSecretsStore.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/WatchedSecretsStore.java @@ -111,13 +111,12 @@ public void addLabelsToWatchedSecrets() { Log.infof("Adding label to Secret \"%s\"", secret.getMetadata().getName()); - secret = new SecretBuilder(secret) - .editMetadata() - .addToLabels(Constants.KEYCLOAK_COMPONENT_LABEL, WATCHED_SECRETS_LABEL_VALUE) - .endMetadata() - .build(); - - client.secrets().inNamespace(secret.getMetadata().getNamespace()).withName(secret.getMetadata().getName()).patch(secret); + client.secrets().inNamespace(secret.getMetadata().getNamespace()).withName(secret.getMetadata().getName()) + .edit(s -> new SecretBuilder(s) + .editMetadata() + .addToLabels(Constants.KEYCLOAK_COMPONENT_LABEL, WATCHED_SECRETS_LABEL_VALUE) + .endMetadata() + .build()); } } } @@ -194,8 +193,13 @@ public static EventSource getStoreEventSource(KubernetesClient client, String na } private static void cleanObsoleteLabelFromSecret(KubernetesClient client, Secret secret) { - secret.getMetadata().getLabels().remove(Constants.KEYCLOAK_COMPONENT_LABEL); - client.secrets().inNamespace(secret.getMetadata().getNamespace()).withName(secret.getMetadata().getName()).patch(secret); + client.secrets().inNamespace(secret.getMetadata().getNamespace()).withName(secret.getMetadata().getName()) + .edit(s -> new SecretBuilder(s) + .editMetadata() + .removeFromLabels(Constants.KEYCLOAK_COMPONENT_LABEL) + .endMetadata() + .build() + ); } public static EventSource getWatchedSecretsEventSource(KubernetesClient client, String namespace) { diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java index 93534231d9c4..70785add7472 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; import org.keycloak.operator.Constants; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; @@ -103,8 +104,11 @@ public static void before() throws FileNotFoundException { } @BeforeEach - public void beforeEach() { - Log.info(((operatorDeployment == OperatorDeployment.remote) ? "Remote " : "Local ") + "Run Test :" + namespace); + public void beforeEach(TestInfo testInfo) { + String testClassName = testInfo.getTestClass().map(c -> c.getSimpleName() + ".").orElse(""); + Log.info("\n------- STARTING: " + testClassName + testInfo.getDisplayName() + "\n" + + "------- Namespace: " + namespace + "\n" + + "------- Mode: " + ((operatorDeployment == OperatorDeployment.remote) ? "remote" : "local")); } private static void createK8sClient() { diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/RealmImportTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/RealmImportTest.java index 0773b06dac1b..1535dadd8e18 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/RealmImportTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/RealmImportTest.java @@ -24,6 +24,7 @@ import org.awaitility.Awaitility; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.keycloak.operator.testsuite.utils.CRAssert; import org.keycloak.operator.controllers.KeycloakService; @@ -49,8 +50,8 @@ public class RealmImportTest extends BaseOperatorTest { @Override @BeforeEach - public void beforeEach() { - super.beforeEach(); + public void beforeEach(TestInfo testInfo) { + super.beforeEach(testInfo); // Recreating the database and the realm import CR to keep this test isolated k8sclient.load(getClass().getResourceAsStream("/example-realm.yaml")).inNamespace(namespace).delete(); k8sclient.load(getClass().getResourceAsStream("/incorrect-realm.yaml")).inNamespace(namespace).delete(); From f87a499c33c71f11353179a321a0c6ffce484507 Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Tue, 15 Nov 2022 09:29:26 +0100 Subject: [PATCH 06/21] Backport fixes from profile refactor (#15495) --- .../protocol/oidc/OIDCLoginProtocolFactory.java | 15 ++++++++------- .../migration/DefaultMigrationProvider.java | 15 ++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) 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 23a959e55209..0adc9e623a3c 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java @@ -110,6 +110,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { @Override public void init(Config.Scope config) { + initBuiltIns(); this.providerConfig = new OIDCProviderConfig(config); if (providerConfig.isLegacyLogoutRedirectUri()) { logger.warnf("Deprecated switch '%s' is enabled. Please try to disable it and update your clients to use OpenID Connect compliant way for RP-initiated logout.", CONFIG_LEGACY_LOGOUT_REDIRECT_URI); @@ -129,9 +130,9 @@ public Map getBuiltinMappers() { return builtins; } - static Map builtins = new HashMap<>(); + private Map builtins = new HashMap<>(); - static { + void initBuiltIns() { ProtocolMapperModel model; model = UserPropertyMapper.createClaimMapper(USERNAME, "username", @@ -218,7 +219,7 @@ public Map getBuiltinMappers() { } } - private static void createUserAttributeMapper(String name, String attrName, String claimName, String type) { + private void createUserAttributeMapper(String name, String attrName, String claimName, String type) { ProtocolMapperModel model = UserAttributeMapper.createClaimMapper(name, attrName, claimName, type, @@ -297,7 +298,7 @@ protected void createDefaultClientScopesImpl(RealmModel newRealm) { } - public static ClientScopeModel addRolesClientScope(RealmModel newRealm) { + public ClientScopeModel addRolesClientScope(RealmModel newRealm) { ClientScopeModel rolesScope = KeycloakModelUtils.getClientScopeByName(newRealm, ROLES_SCOPE); if (rolesScope == null) { rolesScope = newRealm.addClientScope(ROLES_SCOPE); @@ -320,7 +321,7 @@ public static ClientScopeModel addRolesClientScope(RealmModel newRealm) { } - public static ClientScopeModel addWebOriginsClientScope(RealmModel newRealm) { + public ClientScopeModel addWebOriginsClientScope(RealmModel newRealm) { ClientScopeModel originsScope = KeycloakModelUtils.getClientScopeByName(newRealm, WEB_ORIGINS_SCOPE); if (originsScope == null) { originsScope = newRealm.addClientScope(WEB_ORIGINS_SCOPE); @@ -347,7 +348,7 @@ public static ClientScopeModel addWebOriginsClientScope(RealmModel newRealm) { * @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) { + public ClientScopeModel addMicroprofileJWTClientScope(RealmModel newRealm) { ClientScopeModel microprofileScope = KeycloakModelUtils.getClientScopeByName(newRealm, MICROPROFILE_JWT_SCOPE); if (microprofileScope == null) { microprofileScope = newRealm.addClientScope(MICROPROFILE_JWT_SCOPE); @@ -366,7 +367,7 @@ public static ClientScopeModel addMicroprofileJWTClientScope(RealmModel newRealm } - public static void addAcrClientScope(RealmModel newRealm) { + public void addAcrClientScope(RealmModel newRealm) { if (Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) { ClientScopeModel acrScope = KeycloakModelUtils.getClientScopeByName(newRealm, ACR_SCOPE); if (acrScope == null) { 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 f16afcf4af0e..74a8d5b0b294 100755 --- a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java +++ b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java @@ -23,17 +23,15 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; -import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.LoginProtocolFactory; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; -import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.services.managers.RealmManager; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -84,26 +82,29 @@ public void setupAdminCli(RealmModel realm) { new RealmManager(session).setupAdminCli(realm); } + private OIDCLoginProtocolFactory getOIDCLoginProtocolFactory() { + return (OIDCLoginProtocolFactory) session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, OIDCLoginProtocol.LOGIN_PROTOCOL); + } @Override public ClientScopeModel addOIDCRolesClientScope(RealmModel realm) { - return OIDCLoginProtocolFactory.addRolesClientScope(realm); + return getOIDCLoginProtocolFactory().addRolesClientScope(realm); } @Override public ClientScopeModel addOIDCWebOriginsClientScope(RealmModel realm) { - return OIDCLoginProtocolFactory.addWebOriginsClientScope(realm); + return getOIDCLoginProtocolFactory().addWebOriginsClientScope(realm); } @Override public ClientScopeModel addOIDCMicroprofileJWTClientScope(RealmModel realm) { - return OIDCLoginProtocolFactory.addMicroprofileJWTClientScope(realm); + return getOIDCLoginProtocolFactory().addMicroprofileJWTClientScope(realm); } @Override public void addOIDCAcrClientScope(RealmModel realm) { - OIDCLoginProtocolFactory.addAcrClientScope(realm); + getOIDCLoginProtocolFactory().addAcrClientScope(realm); } @Override From 3b06c097b6ff94fb32c2ad496db5044c589552ae Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 18 Nov 2022 07:56:50 +0100 Subject: [PATCH 07/21] Stop adding .v2 to default theme if set in server config (#15501) Closes #15392 --- .../keycloak/theme/ThemeSelectorProvider.java | 21 +++++++++++++------ .../theme/DefaultThemeSelectorProvider.java | 2 -- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/server-spi/src/main/java/org/keycloak/theme/ThemeSelectorProvider.java b/server-spi/src/main/java/org/keycloak/theme/ThemeSelectorProvider.java index 4a96cd1b98a7..4e49c4d4a35a 100755 --- a/server-spi/src/main/java/org/keycloak/theme/ThemeSelectorProvider.java +++ b/server-spi/src/main/java/org/keycloak/theme/ThemeSelectorProvider.java @@ -19,7 +19,6 @@ import org.keycloak.Config; import org.keycloak.common.Profile; -import org.keycloak.common.Version; import org.keycloak.provider.Provider; /** @@ -27,6 +26,9 @@ */ public interface ThemeSelectorProvider extends Provider { + String DEFAULT = "keycloak"; + String DEFAULT_V2 = "keycloak.v2"; + /** * Return the theme name to use for the specified type * @@ -36,13 +38,20 @@ public interface ThemeSelectorProvider extends Provider { String getThemeName(Theme.Type type); default String getDefaultThemeName(Theme.Type type) { - String name = Config.scope("theme").get("default", Version.NAME.toLowerCase()); + String name = Config.scope("theme").get("default"); + if (name != null && !name.isEmpty()) { + return name; + } + if ((type == Theme.Type.ACCOUNT) && Profile.isFeatureEnabled(Profile.Feature.ACCOUNT2)) { - name = name.concat(".v2"); - } else if ((type == Theme.Type.ADMIN) && Profile.isFeatureEnabled(Profile.Feature.ADMIN2)) { - name = name.concat(".v2"); + return DEFAULT_V2; } - return name; + + if ((type == Theme.Type.ADMIN) && Profile.isFeatureEnabled(Profile.Feature.ADMIN2)) { + return DEFAULT_V2; + } + + return DEFAULT; } } diff --git a/services/src/main/java/org/keycloak/theme/DefaultThemeSelectorProvider.java b/services/src/main/java/org/keycloak/theme/DefaultThemeSelectorProvider.java index b1a61dc81bb7..9208fa6fc4b3 100644 --- a/services/src/main/java/org/keycloak/theme/DefaultThemeSelectorProvider.java +++ b/services/src/main/java/org/keycloak/theme/DefaultThemeSelectorProvider.java @@ -1,8 +1,6 @@ package org.keycloak.theme; import org.keycloak.Config; -import org.keycloak.common.Profile; -import org.keycloak.common.Version; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; From 43a3fed8b76a9acd0bf19d441fffdf109f6622eb Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Mon, 21 Nov 2022 16:57:46 +0100 Subject: [PATCH 08/21] Fix query to work on OracleDB CLOB Closes #15528 --- .../events/jpa/JpaEventStoreProvider.java | 17 ++++------------- .../jpa/entities/RealmAttributeEntity.java | 3 ++- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/model/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProvider.java b/model/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProvider.java index 05afdb6bb464..dc8f0e500d9f 100755 --- a/model/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProvider.java +++ b/model/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProvider.java @@ -35,15 +35,9 @@ import org.keycloak.models.jpa.entities.RealmAttributes; import org.keycloak.models.jpa.entities.RealmEntity; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.provider.InvalidationHandler; -import org.keycloak.timer.ScheduledTask; import javax.persistence.EntityManager; -import javax.persistence.Query; import javax.persistence.TypedQuery; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; import java.io.IOException; import java.util.HashMap; import java.util.List; @@ -284,13 +278,10 @@ private static void setAuthDetails(AdminEvent adminEvent, AdminEventEntity admin } protected void clearExpiredAdminEvents() { - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cr = cb.createQuery(RealmAttributeEntity.class); - Root root = cr.from(RealmAttributeEntity.class); - // unable to cast the CLOB to a BIGINT in the select for H2 2.x, therefore comparing strings only in the DB, and filtering again in the next statement - cr.select(root).where(cb.and(cb.equal(root.get("name"),RealmAttributes.ADMIN_EVENTS_EXPIRATION),cb.notEqual(root.get("value"), "0"))); - Map> realms = em.createQuery(cr).getResultStream() - // filtering again on the attribute as paring the CLOB to BIGINT didn't work in H2 2.x + TypedQuery query = em.createNamedQuery("selectRealmAttributesNotEmptyByName", RealmAttributeEntity.class) + .setParameter("name", RealmAttributes.ADMIN_EVENTS_EXPIRATION); + Map> realms = query.getResultStream() + // filtering again on the attribute as parsing the CLOB to BIGINT didn't work in H2 2.x, and it also different on OracleDB .filter(attribute -> { try { return Long.parseLong(attribute.getValue()) > 0; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributeEntity.java index 6474bec11f64..a015b9ba83f5 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributeEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributeEntity.java @@ -36,7 +36,8 @@ * @version $Revision: 1 $ */ @NamedQueries({ - @NamedQuery(name="deleteRealmAttributesByRealm", query="delete from RealmAttributeEntity attr where attr.realm = :realm") + @NamedQuery(name="deleteRealmAttributesByRealm", query="delete from RealmAttributeEntity attr where attr.realm = :realm"), + @NamedQuery(name="selectRealmAttributesNotEmptyByName", query="select ra from RealmAttributeEntity ra WHERE ra.name = :name and length(ra.value) > 0") }) @Table(name="REALM_ATTRIBUTE") @Entity From 7b6ddce4c4d9948fea07213dd42ee2cea88c9317 Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Tue, 22 Nov 2022 15:09:59 +0100 Subject: [PATCH 09/21] Make tests run on Oracle DB on the internal pipeline Closes #15643 --- testsuite/integration-arquillian/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml index 723fc69c2705..b7d3902f440a 100644 --- a/testsuite/integration-arquillian/pom.xml +++ b/testsuite/integration-arquillian/pom.xml @@ -469,6 +469,7 @@ db-allocator-db-mysql + mysql mysql80 false @@ -502,6 +503,7 @@ db-allocator-db-postgres + postgres postgresql132 false @@ -509,6 +511,7 @@ db-allocator-db-postgresplus + postgres postgresplus131 false @@ -543,6 +546,7 @@ db-allocator-db-mariadb + mariadb mariadb_galera_103 false @@ -577,6 +581,7 @@ db-allocator-db-mssql2019 + mssql mssql2019 false @@ -613,6 +618,7 @@ db-allocator-db-oracleRAC + oracle oracle19cRAC false From 57cbea6e14d083444483219906a1dffff034b52c Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Wed, 23 Nov 2022 17:25:59 +0100 Subject: [PATCH 10/21] Use LOB handling query to select clients on Oracle Closes #15639 --- .../keycloak/models/jpa/JpaRealmProvider.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java index 341427843fb5..a0b46cf7ed60 100644 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java @@ -40,7 +40,7 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; -import org.apache.commons.lang.BooleanUtils; +import org.hibernate.Session; import org.jboss.logging.Logger; import org.keycloak.common.util.Time; import org.keycloak.connections.jpa.util.JpaUtils; @@ -776,6 +776,9 @@ public Stream searchClientsByAttributes(RealmModel realm, Map connection.getMetaData().getDatabaseProductName()); + for (Map.Entry entry : filteredAttributes.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); @@ -783,7 +786,19 @@ public Stream searchClientsByAttributes(RealmModel realm, Map attributeJoin = root.join("attributes"); Predicate attrNamePredicate = builder.equal(attributeJoin.get("name"), key); - Predicate attrValuePredicate = builder.equal(attributeJoin.get("value"), value); + + Predicate attrValuePredicate; + if (dbProductName.equals("Oracle")) { + // SELECT * FROM client_attributes WHERE ... DBMS_LOB.COMPARE(value, '0') = 0 ...; + // Oracle is not able to compare a CLOB with a VARCHAR unless it being converted with TO_CHAR + // But for this all values in the table need to be smaller than 4K, otherwise the cast will fail with + // "ORA-22835: Buffer too small for CLOB to CHAR" (even if it is in another row). + // This leaves DBMS_LOB.COMPARE as the option to compare the CLOB with the value. + attrValuePredicate = builder.equal(builder.function("DBMS_LOB.COMPARE", Integer.class, attributeJoin.get("value"), builder.literal(value)), 0); + } else { + attrValuePredicate = builder.equal(attributeJoin.get("value"), value); + } + predicates.add(builder.and(attrNamePredicate, attrValuePredicate)); } From ac29cbc65daabca5c670c2a1f2a73548286dbad6 Mon Sep 17 00:00:00 2001 From: douph1 <45632394+douph1@users.noreply.github.com> Date: Tue, 29 Nov 2022 09:50:57 +0100 Subject: [PATCH 11/21] Use org.keycloak.common.util.Base64Url to encode/decode clientID fix #15734 related #10227 #10231 --- .../provider/util/IdentityBrokerState.java | 10 +++++----- .../util/IdentityBrokerStateTest.java | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/util/IdentityBrokerState.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/util/IdentityBrokerState.java index 07af68223133..524b45b48dbf 100644 --- a/server-spi-private/src/main/java/org/keycloak/broker/provider/util/IdentityBrokerState.java +++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/util/IdentityBrokerState.java @@ -20,10 +20,10 @@ import org.keycloak.authorization.policy.evaluation.Realm; import org.keycloak.models.ClientModel; import org.keycloak.models.RealmModel; +import org.keycloak.common.util.Base64Url; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; -import java.util.Base64; import java.util.UUID; import java.util.regex.Pattern; @@ -51,8 +51,8 @@ public static IdentityBrokerState decoded(String state, String clientId, String bb.putLong(clientDbUuid.getMostSignificantBits()); bb.putLong(clientDbUuid.getLeastSignificantBits()); byte[] clientUuidBytes = bb.array(); - clientIdEncoded = Base64.getEncoder().encodeToString(clientUuidBytes).replace("=", ""); - } catch (IllegalArgumentException e) { + clientIdEncoded = Base64Url.encode(clientUuidBytes); + } catch (RuntimeException e) { // Ignore...the clientid in the database was not in UUID format. Just use as is. } } @@ -73,7 +73,7 @@ public static IdentityBrokerState encoded(String encodedState, RealmModel realmM try { // If this decoding succeeds it was the result of the encoding of a UUID client.id - if it fails we interpret it as client.clientId // in accordance to the method decoded above - byte[] decodedClientId = Base64.getDecoder().decode(clientId); + byte[] decodedClientId = Base64Url.decode(clientId); ByteBuffer bb = ByteBuffer.wrap(decodedClientId); long first = bb.getLong(); long second = bb.getLong(); @@ -83,7 +83,7 @@ public static IdentityBrokerState encoded(String encodedState, RealmModel realmM if (clientModel != null) { clientId = clientModel.getClientId(); } - } catch (IllegalArgumentException | BufferUnderflowException e) { + } catch (RuntimeException e) { // Ignore...the clientid was not in encoded uuid format. Just use as it is. } } diff --git a/server-spi-private/src/test/java/org/keycloak/broker/provider/util/IdentityBrokerStateTest.java b/server-spi-private/src/test/java/org/keycloak/broker/provider/util/IdentityBrokerStateTest.java index 29adcf743737..282c4edd76c0 100644 --- a/server-spi-private/src/test/java/org/keycloak/broker/provider/util/IdentityBrokerStateTest.java +++ b/server-spi-private/src/test/java/org/keycloak/broker/provider/util/IdentityBrokerStateTest.java @@ -45,6 +45,25 @@ public void testDecodedWithClientIdAnActualUuid() { Assert.assertEquals("gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk.vpISZLVDAc0.7UlEjBTPRx6oOgY9DcO8jA", encodedState.getEncoded()); } + @Test + public void testDecodedWithClientIdAnActualUuidBASE64UriFriendly() { + + // Given + String state = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk"; + String clientId = "c5ac1ea7-6c28-4be1-b7cd-d63a1ba57f78"; + String clientClientId = "http://i.am.an.url"; + String tabId = "vpISZLVDAc0"; + + // When + IdentityBrokerState encodedState = IdentityBrokerState.decoded(state, clientId, clientClientId, tabId); + + // Then + Assert.assertNotNull(encodedState); + Assert.assertEquals(clientClientId, encodedState.getClientId()); + Assert.assertEquals(tabId, encodedState.getTabId()); + Assert.assertEquals("gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk.vpISZLVDAc0.xawep2woS-G3zdY6G6V_eA", encodedState.getEncoded()); + } + @Test public void testEncodedWithClientIdUUid() { // Given From 10813dbbff026ccfab48090b1abf26f13cbfa81a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 29 Nov 2022 14:20:21 +0100 Subject: [PATCH 12/21] Fix OpenshiftClientStorageTest.testCodeGrantFlowWithServiceAccountUsingOAuthRedirectReference (#15741) Closes #15565 --- .../testsuite/openshift/OpenshiftClientStorageTest.java | 6 +++--- .../openshift/client-storage/route-response.json | 8 ++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/openshift/OpenshiftClientStorageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/openshift/OpenshiftClientStorageTest.java index 537fb9bdc904..e3102800a549 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/openshift/OpenshiftClientStorageTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/openshift/OpenshiftClientStorageTest.java @@ -173,12 +173,12 @@ public void clientConfiguration() { @Test public void testCodeGrantFlowWithServiceAccountUsingOAuthRedirectReference() { String clientId = "system:serviceaccount:default:sa-oauth-redirect-reference"; - testCodeGrantFlow(clientId, "https://myapp.org/callback", () -> assertSuccessfulResponseWithoutConsent(clientId)); + testCodeGrantFlow(clientId, "http://127.0.0.1:8180/callback", () -> assertSuccessfulResponseWithoutConsent(clientId)); } @Test public void failCodeGrantFlowWithServiceAccountUsingOAuthRedirectReference() throws Exception { - testCodeGrantFlow("system:serviceaccount:default:sa-oauth-redirect-reference", "http://myapp.org/callback", () -> assertEquals(OAuthErrorException.INVALID_REDIRECT_URI, events.poll().getError())); + testCodeGrantFlow("system:serviceaccount:default:sa-oauth-redirect-reference", "http://invalid/callback", () -> assertEquals(OAuthErrorException.INVALID_REDIRECT_URI, events.poll().getError())); } @Test @@ -214,7 +214,7 @@ public void testCodeGrantFlowWithUserConsent() { @Test public void failCodeGrantFlowWithServiceAccountUsingOAuthRedirectUri() throws Exception { - testCodeGrantFlow("system:serviceaccount:default:sa-oauth-redirect-uri", "http://myapp.org/callback", () -> assertEquals(OAuthErrorException.INVALID_REDIRECT_URI, events.poll().getError())); + testCodeGrantFlow("system:serviceaccount:default:sa-oauth-redirect-uri", "http://invalid/callback", () -> assertEquals(OAuthErrorException.INVALID_REDIRECT_URI, events.poll().getError())); } private void testCodeGrantFlow(String clientId, String expectedRedirectUri, Runnable assertThat) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/openshift/client-storage/route-response.json b/testsuite/integration-arquillian/tests/base/src/test/resources/openshift/client-storage/route-response.json index 8810603fe092..8e0bdd30c283 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/openshift/client-storage/route-response.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/openshift/client-storage/route-response.json @@ -13,22 +13,18 @@ } }, "spec": { - "host": "myapp.org", + "host": "127.0.0.1", "to": { "kind": "Service", "name": "proxy", "weight": 100 }, - "tls": { - "termination": "reencrypt", - "destinationCACertificate": "-----BEGIN COMMENT-----\nThis is an empty PEM file created to provide backwards compatibility\nfor reencrypt routes that have no destinationCACertificate. This \ncontent will only appear for routes accessed via /oapi/v1/routes.\n-----END COMMENT-----\n" - }, "wildcardPolicy": "None" }, "status": { "ingress": [ { - "host": "myapp.org", + "host": "127.0.0.1", "routerName": "router", "conditions": [ { From da8d61616399d5c4e4c470c16dd6b0bd98f2b656 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 13 Dec 2022 14:34:50 +0100 Subject: [PATCH 13/21] Sync commits (#15981) * Avoid path traversal vis double-url encoding of redirect URI (#8) (cherry picked from commit a2128fb9e940d96c2f9a64edcd4fbcc768eedb4f) * Do not resolve user session if corresponding auth session does not exist (#7) * Stabilizing the ConcurrentLoginTest when running with JPA map storage by locking user sessions (#9) Co-authored-by: Marek Posolda Co-authored-by: Pedro Igor Co-authored-by: Alexander Schwartz --- .../oidc/endpoints/LogoutEndpoint.java | 8 +- .../protocol/oidc/utils/RedirectUtils.java | 39 ++++++ .../AuthenticationSessionManager.java | 14 +- .../managers/UserSessionCrossDCManager.java | 14 ++ .../services/resources/SessionCodeChecks.java | 15 +++ .../testsuite/oauth/OAuthRedirectUriTest.java | 17 ++- .../oauth/RPInitiatedLogoutTest.java | 12 +- .../testsuite/oauth/RefreshTokenTest.java | 123 ++++++++++++++++++ 8 files changed, 233 insertions(+), 9 deletions(-) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java index 5bf5aa587ec4..bf3697fd0613 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java @@ -379,6 +379,8 @@ public Response logoutConfirmAction() { session.getProvider(LoginFormsProvider.class).setAttribute(Constants.SKIP_LINK, true); } + event.error(Errors.SESSION_EXPIRED); + return ErrorPage.error(session, logoutSession, Response.Status.BAD_REQUEST, Messages.FAILED_LOGOUT); } @@ -414,6 +416,7 @@ public Response logoutConfirmGet() { AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, false); if (authResult != null) { + event.error(Errors.LOGOUT_FAILED); return ErrorPage.error(session, logoutSession, Response.Status.BAD_REQUEST, Messages.FAILED_LOGOUT); } else { // Probably changing locale on logout screen after logout was already performed. If there is no session in the browser, we can just display that logout was already finished @@ -440,7 +443,10 @@ private Response doBrowserLogout(AuthenticationSessionModel logoutSession) { try { userSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, userSessionIdFromIdToken)); - if (userSession != null) { + if (userSession == null) { + event.event(EventType.LOGOUT); + event.error(Errors.SESSION_EXPIRED); + } else { Integer idTokenIssuedAt = Integer.parseInt(idTokenIssuedAtStr); checkTokenIssuedAt(idTokenIssuedAt, userSession); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java index 01d6cc4c0d5e..9cb314b2e483 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java @@ -18,6 +18,8 @@ package org.keycloak.protocol.oidc.utils; import org.jboss.logging.Logger; +import org.keycloak.common.util.Encode; +import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.UriUtils; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; @@ -91,6 +93,7 @@ public static String verifyRedirectUri(KeycloakSession session, String rootUrl, KeycloakUriInfo uriInfo = session.getContext().getUri(); RealmModel realm = session.getContext().getRealm(); + redirectUri = decodeRedirectUri(redirectUri); if (redirectUri != null) { try { URI uri = URI.create(redirectUri); @@ -152,6 +155,42 @@ public static String verifyRedirectUri(KeycloakSession session, String rootUrl, } } + // Decode redirectUri. We don't decode query and fragment as those can be encoded in the original URL. + // URL can be decoded multiple times (in case it was encoded multiple times, or some of it's parts were encoded multiple times) + private static String decodeRedirectUri(String redirectUri) { + if (redirectUri == null) return null; + int MAX_DECODING_COUNT = 5; // Max count of attempts for decoding URL (in case it was encoded multiple times) + + try { + KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(redirectUri); + String origQuery = uriBuilder.getQuery(); + String origFragment = uriBuilder.getFragment(); + String encodedRedirectUri = uriBuilder + .replaceQuery(null) + .fragment(null) + .buildAsString(); + String decodedRedirectUri = null; + + for (int i = 0; i < MAX_DECODING_COUNT; i++) { + decodedRedirectUri = Encode.decode(encodedRedirectUri); + if (decodedRedirectUri.equals(encodedRedirectUri)) { + // URL is decoded. We can return it (after attach original query and fragment) + return KeycloakUriBuilder.fromUri(decodedRedirectUri) + .replaceQuery(origQuery) + .fragment(origFragment) + .buildAsString(); + } else { + // Next attempt + encodedRedirectUri = decodedRedirectUri; + } + } + } catch (IllegalArgumentException iae) { + logger.debugf("Illegal redirect URI used: %s, Details: %s", redirectUri, iae.getMessage()); + } + logger.debugf("Was not able to decode redirect URI: %s", redirectUri); + return null; + } + private static String lowerCaseHostname(String redirectUri) { int n = redirectUri.indexOf('/', 7); if (n == -1) { diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java index 602ca126b2ad..c90ed16f2e68 100644 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java @@ -34,6 +34,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification; @@ -196,7 +197,18 @@ List getAuthSessionCookies(RealmModel realm) { log.debugf("Not found AUTH_SESSION_ID cookie"); } - return authSessionIds; + return authSessionIds.stream().filter(new Predicate() { + @Override + public boolean test(String id) { + StickySessionEncoderProvider encoder = session.getProvider(StickySessionEncoderProvider.class); + // in case the id is encoded with a route when running in a cluster + String decodedId = encoder.decodeSessionId(cookiesVal.iterator().next()); + // we can't blindly trust the cookie and assume it is valid and referencing a valid root auth session + // but make sure the root authentication session actually exists + // without this check there is a risk of resolving user sessions from invalid root authentication sessions as they share the same id + return session.authenticationSessions().getRootAuthenticationSession(realm, decodedId) != null; + } + }).collect(Collectors.toList()); } diff --git a/services/src/main/java/org/keycloak/services/managers/UserSessionCrossDCManager.java b/services/src/main/java/org/keycloak/services/managers/UserSessionCrossDCManager.java index 9e3eda42be2a..6d6cacbdda6c 100644 --- a/services/src/main/java/org/keycloak/services/managers/UserSessionCrossDCManager.java +++ b/services/src/main/java/org/keycloak/services/managers/UserSessionCrossDCManager.java @@ -17,6 +17,7 @@ package org.keycloak.services.managers; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -26,6 +27,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserSessionModel; +import static org.keycloak.services.managers.AuthenticationManager.authenticateIdentityCookie; import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification; /** @@ -65,6 +67,18 @@ public UserSessionModel getUserSessionWithClient(RealmModel realm, String id, St public UserSessionModel getUserSessionIfExistsRemotely(AuthenticationSessionManager asm, RealmModel realm) { List sessionCookies = asm.getAuthSessionCookies(realm); + if (sessionCookies.isEmpty()) { + // ideally, we should not rely on auth session id to retrieve user sessions + // in case the auth session was removed, we fall back to the identity cookie + // we are here doing the user session lookup twice, however the second lookup is going to make sure the + // session exists in remote caches + AuthenticationManager.AuthResult authResult = lockUserSessionsForModification(kcSession, () -> authenticateIdentityCookie(kcSession, realm, true)); + + if (authResult != null && authResult.getSession() != null) { + sessionCookies = Collections.singletonList(authResult.getSession().getId()); + } + } + return sessionCookies.stream().map(oldEncodedId -> { AuthSessionId authSessionId = asm.decodeAuthSessionId(oldEncodedId); String sessionId = authSessionId.getDecodedId(); diff --git a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java index a670dacf3a06..38e6c168cf0b 100644 --- a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java +++ b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java @@ -17,6 +17,10 @@ package org.keycloak.services.resources; +import static org.keycloak.services.managers.AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE; +import static org.keycloak.services.managers.AuthenticationManager.authenticateIdentityCookie; +import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification; + import java.net.URI; import javax.ws.rs.core.Cookie; @@ -42,11 +46,13 @@ import org.keycloak.protocol.RestartLoginCookie; import org.keycloak.services.ErrorPage; import org.keycloak.services.ServicesLogger; +import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationSessionManager; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; import org.keycloak.services.util.BrowserHistoryHelper; import org.keycloak.services.util.AuthenticationFlowURLHelper; +import org.keycloak.services.util.CookieHelper; import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.RootAuthenticationSessionModel; @@ -178,6 +184,15 @@ public AuthenticationSessionModel initialVerifyAuthSession() { // See if we are already authenticated and userSession with same ID exists. UserSessionModel userSession = authSessionManager.getUserSessionFromAuthCookie(realm); + if (userSession == null) { + // fallback to check if there is an identity cookie + AuthenticationManager.AuthResult authResult = lockUserSessionsForModification(session, () -> authenticateIdentityCookie(session, realm, false)); + + if (authResult != null) { + userSession = authResult.getSession(); + } + } + if (userSession != null) { LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class).setAuthenticationSession(authSession) .setSuccess(Messages.ALREADY_LOGGED_IN); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java index 93022679d9a1..2959c4977b84 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java @@ -337,6 +337,20 @@ public void testWildcard() throws IOException { checkRedirectUri("http://localhost:8280/foo/bar", true, true); checkRedirectUri("http://example.com/foobar", false); checkRedirectUri("http://localhost:8280/foobar", false, true); + + checkRedirectUri("http://example.com/foo/../", false); + checkRedirectUri("http://example.com/foo/%2E%2E/", false); // url-encoded "http://example.com/foobar/../" + checkRedirectUri("http://example.com/foo%2F%2E%2E%2F", false); // url-encoded "http://example.com/foobar/../" + checkRedirectUri("http://example.com/foo/%252E%252E/", false); // double-encoded "http://example.com/foobar/../" + checkRedirectUri("http://example.com/foo/%252E%252E/?some_query_param=some_value", false); // double-encoded "http://example.com/foobar/../?some_query_param=some_value" + checkRedirectUri("http://example.com/foo/%252E%252E/?encodeTest=a%3Cb", false); // double-encoded "http://example.com/foobar/../?encodeTest=a Date: Mon, 7 Nov 2022 10:08:23 +0100 Subject: [PATCH 14/21] Ignore test until the intermittent failure from #14917 is resolved --- .../org/keycloak/testsuite/model/authz/ConcurrentAuthzTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/authz/ConcurrentAuthzTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/authz/ConcurrentAuthzTest.java index d3fa2c744c8f..31c795abd67e 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/authz/ConcurrentAuthzTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/authz/ConcurrentAuthzTest.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.model.authz; +import org.junit.Ignore; import org.junit.Test; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; @@ -132,6 +133,7 @@ public void testPermissionRemoved() { } @Test + @Ignore // This is ignored due to intermittent failure, see https://github.com/keycloak/keycloak/issues/14917 public void testStaleCacheConcurrent() { String permissionId = withRealm(realmId, (session, realm) -> { AuthorizationProvider authorization = session.getProvider(AuthorizationProvider.class); From e01e25f823a3df44828732780522bd3a4156b6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Muzik=C3=A1=C5=99?= Date: Mon, 12 Dec 2022 13:19:55 +0100 Subject: [PATCH 15/21] Update to Quarkus 2.13.5 --- pom.xml | 2 +- quarkus/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0813862e569e..3d7ba0d48e78 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ jboss-snapshots-repository https://s01.oss.sonatype.org/content/repositories/snapshots/ - 2.13.3.Final + 2.13.5.Final ${timestamp} diff --git a/quarkus/pom.xml b/quarkus/pom.xml index cdd07e2851aa..7dbd7db6eb76 100644 --- a/quarkus/pom.xml +++ b/quarkus/pom.xml @@ -36,7 +36,7 @@ Their names should match the name used by Quarkus BOM and the versions are automatically set when running the 'set-quarkus-version.sh' script. --> 4.7.7.Final - 5.6.12.Final + 5.6.14.Final 8.0.30 42.5.0 1.5.4.Final-format-001 From daf0dee168afa9e92a12047b31410f554caa1c26 Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Tue, 15 Nov 2022 09:22:04 +0100 Subject: [PATCH 16/21] Disabling unstable test until further analysis is complete Relates to #15487 --- .../src/test/java/org/keycloak/testsuite/model/DBLockTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/DBLockTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/DBLockTest.java index 2cd4f091a6b9..399313256edb 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/DBLockTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/DBLockTest.java @@ -23,6 +23,7 @@ import org.jboss.logging.Logger; import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; @@ -115,6 +116,7 @@ public void testLockConcurrentlyGeneral() throws Exception { } @Test + @Ignore("Ignored as it is unstable. Analysis pending in https://github.com/keycloak/keycloak/issues/15487") public void testLockConcurrentlyOffline() throws Exception { inComittedTransaction(1, (session , i) -> { testLockConcurrentlyInternal(session, DBLockProvider.Namespace.OFFLINE_SESSIONS); From 6197d05a0d432ad1bf0386b7139d05e22f105970 Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Mon, 21 Nov 2022 10:48:55 +0100 Subject: [PATCH 17/21] Disabling unstable test until further analysis is complete Relates to #15487 --- .../src/test/java/org/keycloak/testsuite/model/DBLockTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/DBLockTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/DBLockTest.java index 399313256edb..5d293264aa70 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/DBLockTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/DBLockTest.java @@ -108,6 +108,7 @@ public void simpleNestedLockTest() throws Exception { } @Test + @Ignore("Ignored as it is unstable. Analysis pending in https://github.com/keycloak/keycloak/issues/15487") public void testLockConcurrentlyGeneral() throws Exception { inComittedTransaction(1, (session , i) -> { testLockConcurrentlyInternal(session, DBLockProvider.Namespace.DATABASE); From 7e206a4be04bb35dd52350077a876688420364d1 Mon Sep 17 00:00:00 2001 From: Peter Zaoral Date: Wed, 30 Nov 2022 00:04:56 +0100 Subject: [PATCH 18/21] Cleanup dependencies and align with Quarkus * aligned parent POM dependency versions with the Quarkus BOM Closes #15325 Signed-off-by: Peter Zaoral Conflicts: pom.xml testsuite/integration-arquillian/pom.xml testsuite/utils/pom.xml --- model/map-jpa/pom.xml | 2 +- pom.xml | 34 +++++++++++++----------- quarkus/pom.xml | 17 +----------- quarkus/runtime/pom.xml | 3 --- testsuite/integration-arquillian/pom.xml | 14 +++++----- testsuite/model/pom.xml | 2 +- testsuite/utils/pom.xml | 12 ++++++--- 7 files changed, 38 insertions(+), 46 deletions(-) diff --git a/model/map-jpa/pom.xml b/model/map-jpa/pom.xml index 30e6cb40fef6..93bc3d758aef 100644 --- a/model/map-jpa/pom.xml +++ b/model/map-jpa/pom.xml @@ -57,7 +57,7 @@ org.hibernate.orm.tooling hibernate-enhance-maven-plugin - ${hibernate.core.version} + ${hibernate-orm.version} diff --git a/pom.xml b/pom.xml index 3d7ba0d48e78..f20586737499 100644 --- a/pom.xml +++ b/pom.xml @@ -79,8 +79,8 @@ 2.1.3 2.1.214 2.2.3 - 5.6.10.Final - 5.6.10.Final + 5.6.14.Final + ${hibernate-orm.version} 13.0.10.Final 4.4.1.Final 1.3.2 @@ -96,7 +96,7 @@ 2.0.1.Final 2.0.0.Final 1.2.17 - 4.7.4.Final + 4.7.7.Final ${resteasy.version} 20211018.2 1.7.30 @@ -108,7 +108,7 @@ 1.2.2 2.3.3-b02 2.2.19.Final - 1.18.3.Final + 1.20.1.Final 1.9.0.Final 9.4.40.v20210413 6.0.3 @@ -143,13 +143,15 @@ 8.0.23 - 8.0.28 + 8.0.30 13.2 - 42.3.3 + 42.5.0 10.3.27 - 2.7.2 + 2.7.2 2019-CU10-ubuntu-20.04 - 9.2.0.jre8 + 9.2.0.jre8 + + 21.5.0.0 1.3.1b @@ -158,6 +160,8 @@ 4.13.2 2.7.0.Final 2.35.0 + + 3.0.0-M7 1.4.01 3.1.7 4.1.1 @@ -165,8 +169,8 @@ - 2.3 - 1.16.3 + 3.0.1 + 1.17.5 1.3.5 @@ -571,7 +575,7 @@ org.hibernate hibernate-core - ${hibernate.core.version} + ${hibernate-orm.version} org.hibernate @@ -731,7 +735,7 @@ mysql mysql-connector-java - ${mysql.driver.version} + ${mysql-jdbc.version} test @@ -902,7 +906,7 @@ org.wildfly.security wildfly-elytron - ${elytron.version} + ${wildfly-elytron.version} org.wildfly.common @@ -1762,7 +1766,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M7 + ${surefire-plugin.version} once -Djava.awt.headless=true ${surefire.memory.settings} ${surefire.system.args} -Duser.language=en -Duser.region=US @@ -1972,7 +1976,7 @@ org.apache.maven.plugins maven-javadoc-plugin - -Xdoclint:none + none false cx.*:org.freedesktop*:org.jvnet* diff --git a/quarkus/pom.xml b/quarkus/pom.xml index 7dbd7db6eb76..549d514d93a4 100644 --- a/quarkus/pom.xml +++ b/quarkus/pom.xml @@ -31,25 +31,12 @@ pom - - 4.7.7.Final - 5.6.14.Final - 8.0.30 - 42.5.0 1.5.4.Final-format-001 - 1.20.1.Final - 3.0.1 - ${hibernate-orm.version} - ${mysql-jdbc.version} - ${postgresql-jdbc.version} ${wildfly-common.version} - 3.0.0-M5 @@ -107,6 +91,7 @@ ${infinispan.version} + org.jboss.spec.javax.xml.bind diff --git a/quarkus/runtime/pom.xml b/quarkus/runtime/pom.xml index e65b09e1124f..1efab26febe7 100644 --- a/quarkus/runtime/pom.xml +++ b/quarkus/runtime/pom.xml @@ -91,7 +91,6 @@ org.wildfly.security wildfly-elytron - ${wildfly-elytron.version} io.quarkiverse.vault @@ -345,7 +344,6 @@ org.jboss.resteasy resteasy-multipart-provider - ${resteasy.version} org.jboss.logging @@ -700,7 +698,6 @@ org.apache.maven.plugins maven-failsafe-plugin - ${surefire-plugin.version} diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml index b7d3902f440a..47b16c153195 100644 --- a/testsuite/integration-arquillian/pom.xml +++ b/testsuite/integration-arquillian/pom.xml @@ -458,7 +458,7 @@ mysql mysql-connector-java - ${mysql.driver.version} + ${mysql-jdbc.version} mysql:${mysql.version} 3306 false @@ -492,7 +492,7 @@ org.postgresql postgresql - ${postgresql.driver.version} + ${postgresql-jdbc.version} postgres:${postgresql.version} 5432 false @@ -534,7 +534,7 @@ org.mariadb.jdbc mariadb-java-client - ${mariadb.driver.version} + ${mariadb-jdbc.version} mariadb:${mariadb.version} 3306 false @@ -575,7 +575,7 @@ com.microsoft.sqlserver mssql-jdbc - ${mssql.driver.version} + ${mssql-jdbc.version} @@ -610,9 +610,9 @@ - com.oracle - ojdbc7 - 12.1.0 + com.oracle.database.jdbc + ojdbc11 + ${oracle-jdbc.version} diff --git a/testsuite/model/pom.xml b/testsuite/model/pom.xml index a4a0fa447255..63bf06c019e1 100644 --- a/testsuite/model/pom.xml +++ b/testsuite/model/pom.xml @@ -113,7 +113,7 @@ org.postgresql postgresql - ${postgresql.driver.version} + ${postgresql-jdbc.version} org.testcontainers diff --git a/testsuite/utils/pom.xml b/testsuite/utils/pom.xml index f95fd4dbad1a..48662e5c4835 100755 --- a/testsuite/utils/pom.xml +++ b/testsuite/utils/pom.xml @@ -254,17 +254,23 @@ org.postgresql postgresql - ${postgresql.driver.version} + ${postgresql-jdbc.version} + + + com.oracle.database.jdbc + ojdbc11 + ${oracle-jdbc.version} + compile org.mariadb.jdbc mariadb-java-client - ${mariadb.driver.version} + ${mariadb-jdbc.version} com.microsoft.sqlserver mssql-jdbc - ${mssql.driver.version} + ${mssql-jdbc.version} From 7e767af26afd50fa8c8d1a295c552d03e4fd671e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira da Silva Date: Mon, 19 Dec 2022 18:59:03 -0300 Subject: [PATCH 19/21] Update to Quarkus 2.13.6.Final Resolves #16084 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f20586737499..e506da540286 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ jboss-snapshots-repository https://s01.oss.sonatype.org/content/repositories/snapshots/ - 2.13.5.Final + 2.13.6.Final ${timestamp} @@ -145,7 +145,7 @@ 8.0.23 8.0.30 13.2 - 42.5.0 + 42.5.1 10.3.27 2.7.2 2019-CU10-ubuntu-20.04 From 03a04e62333e9782d8a6e1a6a3e43860a589263c Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 6 Dec 2022 14:50:07 +0100 Subject: [PATCH 20/21] The redirect URI cannot be verified during logout in the case when client was removed closes #15866 (cherry picked from commit def7142c50a2d4671550f76796d8f853253895b3) --- .../oidc/endpoints/LogoutEndpoint.java | 5 ++-- .../oauth/RPInitiatedLogoutTest.java | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java index bf3697fd0613..cdb6461b9d5e 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java @@ -246,10 +246,9 @@ public Response logout(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String OIDCAdvancedConfigWrapper wrapper = OIDCAdvancedConfigWrapper.fromClientModel(client); Set postLogoutRedirectUris = wrapper.getPostLogoutRedirectUris() != null ? new HashSet(wrapper.getPostLogoutRedirectUris()) : new HashSet<>(); validatedRedirectUri = RedirectUtils.verifyRedirectUri(session, client.getRootUrl(), redirectUri, postLogoutRedirectUris, true); - } else if (clientId == null) { + } else if (clientId == null && providerConfig.isLegacyLogoutRedirectUri()) { /* - * Only call verifyRealmRedirectUri, in case both clientId and client are null - otherwise - * the logout uri contains a non-existing client, and we should show an INVALID_REDIRECT_URI error + * Only call verifyRealmRedirectUri against all in the realm, in case when "Legacy" switch is enabled and when we don't have a client - usually due both clientId and client are null */ validatedRedirectUri = RedirectUtils.verifyRealmRedirectUri(session, redirectUri); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RPInitiatedLogoutTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RPInitiatedLogoutTest.java index 0dc67c6e902b..ac93e73a388a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RPInitiatedLogoutTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/RPInitiatedLogoutTest.java @@ -83,6 +83,7 @@ import org.keycloak.testsuite.updaters.ClientAttributeUpdater; import org.keycloak.testsuite.updaters.RealmAttributeUpdater; import org.keycloak.testsuite.updaters.UserAttributeUpdater; +import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientManager; import org.keycloak.testsuite.util.InfinispanTestTimeServiceRule; import org.keycloak.testsuite.util.Matchers; @@ -1068,6 +1069,33 @@ public void testLogoutWhenAccountClientIsDisabled() throws IOException { } } + @Test + public void logoutWithIdTokenAndRemovedClient() throws Exception { + ClientRepresentation clientRep = ClientBuilder.create() + .clientId("my-foo-client") + .enabled(true) + .baseUrl("https://foo/bar") + .addRedirectUri(APP_REDIRECT_URI) + .secret("password") + .build(); + try (Response response = testRealm().clients().create(clientRep)) { + String uuid = ApiUtil.getCreatedId(response); + oauth.clientId("my-foo-client"); + + OAuthClient.AccessTokenResponse tokenResponse = loginUser(); + + // Remove client after login of user + testRealm().clients().get(uuid).remove(); + + String logoutUrl = oauth.getLogoutUrl().postLogoutRedirectUri(APP_REDIRECT_URI).idTokenHint(tokenResponse.getIdToken()).build(); + driver.navigate().to(logoutUrl); + + // Invalid redirect URI page is shown. It was not possible to verify post_logout_redirect_uri due the client was removed + errorPage.assertCurrent(); + events.expectLogoutError(OAuthErrorException.INVALID_REDIRECT_URI).detail(Details.REDIRECT_URI, APP_REDIRECT_URI).assertEvent(); + } + } + // SUPPORT METHODS private OAuthClient.AccessTokenResponse loginUser() { return loginUser(false); From be4553dab3a28e98a034614536905cf06c8ab350 Mon Sep 17 00:00:00 2001 From: stianst Date: Tue, 10 Jan 2023 08:45:48 +0100 Subject: [PATCH 21/21] Update to okhttp 4.9.3 --- quarkus/pom.xml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/quarkus/pom.xml b/quarkus/pom.xml index 549d514d93a4..4c232ee6cd6f 100644 --- a/quarkus/pom.xml +++ b/quarkus/pom.xml @@ -84,6 +84,12 @@ org.infinispan infinispan-server-hotrod ${infinispan.version} + + + com.squareup.okhttp3 + okhttp + + org.infinispan @@ -132,6 +138,33 @@ + + + + com.openshift + openshift-restclient-java + ${version.com.openshift.openshift-restclient-java} + + + log4j + log4j + + + org.slf4j + slf4j-log4j12 + + + com.squareup.okhttp3 + okhttp + + + + + + com.squareup.okhttp3 + okhttp + 4.9.3 +