diff --git a/misc/keycloak-test-helper/pom.xml b/misc/keycloak-test-helper/pom.xml
index 61a22a603d..2524add6e8 100644
--- a/misc/keycloak-test-helper/pom.xml
+++ b/misc/keycloak-test-helper/pom.xml
@@ -37,5 +37,9 @@
selenium-java
provided
+
+ org.keycloak
+ keycloak-services
+
diff --git a/misc/keycloak-test-helper/src/main/java/org/keycloak/test/builders/ClientBuilder.java b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/builders/ClientBuilder.java
index b4c78bcd7a..49a1d6ae58 100644
--- a/misc/keycloak-test-helper/src/main/java/org/keycloak/test/builders/ClientBuilder.java
+++ b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/builders/ClientBuilder.java
@@ -17,6 +17,7 @@
package org.keycloak.test.builders;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.representations.idm.ClientRepresentation;
import java.util.Collections;
@@ -88,6 +89,9 @@ public class ClientBuilder {
if (rep.getRedirectUris() == null && rep.getRootUrl() != null)
rep.setRedirectUris(Collections.singletonList(rep.getRootUrl().concat("/*")));
+ if (OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).getPostLogoutRedirectUris() == null) {
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).setPostLogoutRedirectUris(Collections.singletonList("+"));
+ }
return rep;
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate19_0_0_DefaultPostLogoutRedirectUri.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate19_0_0_DefaultPostLogoutRedirectUri.java
new file mode 100644
index 0000000000..9499c46ed8
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate19_0_0_DefaultPostLogoutRedirectUri.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 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 liquibase.exception.CustomChangeException;
+import liquibase.statement.core.InsertStatement;
+import liquibase.structure.core.Table;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+
+public class JpaUpdate19_0_0_DefaultPostLogoutRedirectUri extends CustomKeycloakTask {
+
+ private static final String POST_LOGOUT_REDIRECT_URIS = "post.logout.redirect.uris";
+
+ @Override
+ protected void generateStatementsImpl() throws CustomChangeException {
+ String sql = "SELECT DISTINCT CLIENT_ID FROM " + getTableName("REDIRECT_URIS");
+
+ try (PreparedStatement statement = jdbcConnection.prepareStatement(sql); ResultSet rs = statement.executeQuery()) {
+ while (rs.next()) {
+ statements.add(
+ new InsertStatement(null, null, database.correctObjectName("CLIENT_ATTRIBUTES", Table.class))
+ .addColumnValue("CLIENT_ID", rs.getString(1))
+ .addColumnValue("NAME", POST_LOGOUT_REDIRECT_URIS)
+ .addColumnValue("VALUE", "+")
+ );
+ }
+ } catch (Exception e) {
+ throw new CustomChangeException(getTaskId() + ": Exception when extracting data from previous version", e);
+ }
+ }
+
+ @Override
+ protected String getTaskId() {
+ return "Default post_logout_redirect_uris (19.0.0)";
+ }
+
+}
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-19.0.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-19.0.0.xml
new file mode 100644
index 0000000000..15adbed54d
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-19.0.0.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index 348d87c9aa..4dc8d77b2a 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -73,5 +73,6 @@
+
diff --git a/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyExportImportManager.java b/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyExportImportManager.java
index 2caa8531ff..19245e82bd 100644
--- a/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyExportImportManager.java
+++ b/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyExportImportManager.java
@@ -55,6 +55,7 @@ import org.keycloak.models.utils.DefaultKeyProviders;
import org.keycloak.models.utils.DefaultRequiredActions;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
@@ -464,6 +465,10 @@ public class LegacyExportImportManager implements ExportImportManager {
Map appMap = new HashMap();
for (ClientRepresentation resourceRep : rep.getClients()) {
ClientModel app = RepresentationToModel.createClient(session, realm, resourceRep, mappedFlows);
+ String postLogoutRedirectUris = app.getAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS);
+ if (postLogoutRedirectUris == null) {
+ app.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+");
+ }
appMap.put(app.getClientId(), app);
ValidationUtil.validateClient(session, app, false, r -> {
diff --git a/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java b/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java
index edb167726d..2c3756e78c 100644
--- a/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java
+++ b/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java
@@ -51,6 +51,7 @@ import org.keycloak.models.utils.DefaultKeyProviders;
import org.keycloak.models.utils.DefaultRequiredActions;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
@@ -449,6 +450,10 @@ public class MapExportImportManager implements ExportImportManager {
Map appMap = new HashMap<>();
for (ClientRepresentation resourceRep : rep.getClients()) {
ClientModel app = RepresentationToModel.createClient(session, realm, resourceRep, mappedFlows);
+ String postLogoutRedirectUris = app.getAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS);
+ if (postLogoutRedirectUris == null) {
+ app.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+");
+ }
appMap.put(app.getClientId(), app);
ValidationUtil.validateClient(session, app, false, r -> {
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/oidc/OIDCConfigAttributes.java b/server-spi-private/src/main/java/org/keycloak/protocol/oidc/OIDCConfigAttributes.java
index 11cb6cbe8a..bf66de0982 100644
--- a/server-spi-private/src/main/java/org/keycloak/protocol/oidc/OIDCConfigAttributes.java
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/oidc/OIDCConfigAttributes.java
@@ -82,6 +82,8 @@ public final class OIDCConfigAttributes {
public static final String FRONT_CHANNEL_LOGOUT_URI = "frontchannel.logout.url";
public static final String FRONT_CHANNEL_LOGOUT_SESSION_REQUIRED = "frontchannel.logout.session.required";
+ public static final String POST_LOGOUT_REDIRECT_URIS = "post.logout.redirect.uris";
+
private OIDCConfigAttributes() {
}
diff --git a/services/src/main/java/org/keycloak/partialimport/ClientsPartialImport.java b/services/src/main/java/org/keycloak/partialimport/ClientsPartialImport.java
index 2382f1c8bb..546349452d 100755
--- a/services/src/main/java/org/keycloak/partialimport/ClientsPartialImport.java
+++ b/services/src/main/java/org/keycloak/partialimport/ClientsPartialImport.java
@@ -24,6 +24,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.PartialImportRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
@@ -118,6 +119,9 @@ public class ClientsPartialImport extends AbstractPartialImport getPostLogoutRedirectUris() {
+ List postLogoutRedirectUris = getAttributeMultivalued(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS);
+ if(postLogoutRedirectUris == null || postLogoutRedirectUris.isEmpty()) {
+ return null;
+ }
+ else if (postLogoutRedirectUris.get(0).equals("+")) {
+ if(clientModel != null) {
+ return new ArrayList(clientModel.getRedirectUris());
+ }
+ else if(clientRep != null) {
+ return clientRep.getRedirectUris();
+ }
+ return null;
+ }
+ else {
+ return postLogoutRedirectUris;
+ }
+ }
+
+ public void setPostLogoutRedirectUris(List postLogoutRedirectUris) {
+ setAttributeMultivalued(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, postLogoutRedirectUris);
+ }
+
}
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 edecd0d920..8c11866ff8 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
@@ -48,6 +48,7 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.SystemClientUtil;
import org.keycloak.protocol.oidc.BackchannelLogoutResponse;
import org.keycloak.protocol.oidc.LogoutTokenValidationCode;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.protocol.oidc.OIDCProviderConfig;
@@ -92,6 +93,15 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.keycloak.models.UserSessionModel.State.LOGGED_OUT;
+import static org.keycloak.models.UserSessionModel.State.LOGGING_OUT;
+import static org.keycloak.services.resources.LoginActionsService.SESSION_CODE;
/**
* @author Stian Thorgersen
@@ -233,7 +243,9 @@ public class LogoutEndpoint {
String validatedRedirectUri = null;
if (redirectUri != null) {
if (client != null) {
- validatedRedirectUri = RedirectUtils.verifyRedirectUri(session, redirectUri, client);
+ 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) {
/*
* Only call verifyRealmRedirectUri, in case both clientId and client are null - otherwise
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
index a756bd03c3..4be269f09c 100755
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
@@ -208,6 +208,10 @@ public class DescriptionConverter {
configWrapper.setTosUri(clientOIDC.getTosUri());
}
+ if (clientOIDC.getPostLogoutRedirectUris() != null) {
+ configWrapper.setPostLogoutRedirectUris(clientOIDC.getPostLogoutRedirectUris());
+ }
+
// CIBA
String backchannelTokenDeliveryMode = clientOIDC.getBackchannelTokenDeliveryMode();
if (backchannelTokenDeliveryMode != null) {
@@ -403,6 +407,9 @@ public class DescriptionConverter {
if (config.getTokenEndpointAuthSigningAlg() != null) {
response.setTokenEndpointAuthSigningAlg(config.getTokenEndpointAuthSigningAlg());
}
+ if (config.getPostLogoutRedirectUris() != null) {
+ response.setPostLogoutRedirectUris(config.getPostLogoutRedirectUris());
+ }
response.setBackchannelLogoutUri(config.getBackchannelLogoutUrl());
response.setBackchannelLogoutSessionRequired(config.isBackchannelLogoutSessionRequired());
response.setBackchannelLogoutSessionRequired(config.getBackchannelLogoutRevokeOfflineTokens());
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 129ebb5dd8..28d4a6c8b4 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -173,6 +173,7 @@ public class RealmManager {
String baseUrl = "/admin/" + realm.getName() + "/console/";
adminConsole.setBaseUrl(baseUrl);
adminConsole.addRedirectUri(baseUrl + "*");
+ adminConsole.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+");
adminConsole.setWebOrigins(Collections.singleton("+"));
adminConsole.setEnabled(true);
@@ -417,6 +418,7 @@ public class RealmManager {
String baseUrl = "/realms/" + realm.getName() + "/account/";
accountClient.setBaseUrl(baseUrl);
accountClient.addRedirectUri(baseUrl + "*");
+ accountClient.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+");
accountClient.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
@@ -451,6 +453,7 @@ public class RealmManager {
accountConsoleClient.setRootUrl(Constants.AUTH_BASE_URL_PROP);
accountConsoleClient.setBaseUrl(baseUrl);
accountConsoleClient.addRedirectUri(baseUrl + "*");
+ accountConsoleClient.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+");
accountConsoleClient.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java
index 3af96f76d5..e46a235822 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/concurrency/ConcurrentLoginTest.java
@@ -54,6 +54,7 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.UserSessionSpi;
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.common.util.Retry;
@@ -98,6 +99,7 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
.directAccessGrants()
.redirectUris("*")
.addWebOrigin("*")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
.secret("password")
.build();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java
index f21c4d6381..1faa992dae 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java
@@ -4,6 +4,7 @@ import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.protocol.ProtocolMapperUtils;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
@@ -72,6 +73,8 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
client.setAdminUrl(getConsumerRoot() +
"/auth/realms/" + REALM_CONS_NAME + "/broker/" + IDP_OIDC_ALIAS + "/endpoint");
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setPostLogoutRedirectUris(Collections.singletonList("+"));
+
ProtocolMapperRepresentation emailMapper = new ProtocolMapperRepresentation();
emailMapper.setName("email");
emailMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
@@ -170,6 +173,8 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
client.setBaseUrl(getConsumerRoot() +
"/auth/realms/" + REALM_CONS_NAME + "/app");
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setPostLogoutRedirectUris(Collections.singletonList("+"));
+
return Collections.singletonList(client);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java
index 16f2b14fe4..d5e784b86c 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java
@@ -633,6 +633,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
clientRep.setPublicClient(Boolean.FALSE);
clientRep.setServiceAccountsEnabled(Boolean.TRUE);
clientRep.setRedirectUris(Collections.singletonList(ServerURLs.getAuthServerContextRoot() + "/auth/realms/master/app/auth"));
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setPostLogoutRedirectUris(Collections.singletonList("+"));
op.accept(clientRep);
Response resp = adminClient.realm(REALM_NAME).clients().create(clientRep);
if (resp.getStatus() == Response.Status.BAD_REQUEST.getStatusCode()) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java
index 06d66ec57a..dfbcd23960 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java
@@ -240,11 +240,14 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest {
.clientId("test-device")
.secret("secret")
.attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
.build();
clients.add(app);
ClientRepresentation appPublic = ClientBuilder.create().id(KeycloakModelUtils.generateId()).publicClient()
- .clientId(DEVICE_APP_PUBLIC).attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .clientId(DEVICE_APP_PUBLIC)
+ .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
.build();
clients.add(appPublic);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
index 56a7b432ad..a87a68c086 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
@@ -52,6 +52,7 @@ import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
@@ -844,4 +845,27 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
realmRep.getAttributes().remove(Constants.ACR_LOA_MAP);
adminClient.realm("test").update(realmRep);
}
+
+ @Test
+ public void testPostLogoutRedirectUri() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+ clientRep.setPostLogoutRedirectUris(Collections.singletonList("http://redirect/logout"));
+ OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ assertEquals("http://redirect/logout", response.getPostLogoutRedirectUris().get(0));
+ }
+
+ @Test
+ public void testPostLogoutRedirectUriPlus() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+ clientRep.setPostLogoutRedirectUris(Collections.singletonList("+"));
+ OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ assertEquals("http://redirect", response.getPostLogoutRedirectUris().get(0));
+ }
+
+ @Test
+ public void testPostLogoutRedirectUriNull() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+ OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ assertNull(response.getPostLogoutRedirectUris());
+ }
}
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 da609d03a4..2c151306da 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
@@ -32,6 +32,7 @@ import org.keycloak.models.LDAPConstants;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.credential.dto.PasswordCredentialData;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
@@ -754,4 +755,14 @@ public class ExportImportUtil {
OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE
));
}
+
+ public static void testDefaultPostLogoutRedirectUris(RealmResource realm) {
+ for (ClientRepresentation client : realm.clients().findAll()) {
+ List redirectUris = client.getRedirectUris();
+ if(redirectUris != null && !redirectUris.isEmpty()) {
+ String postLogoutRedirectUris = client.getAttributes().get(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS);
+ Assert.assertEquals("+", postLogoutRedirectUris);
+ }
+ }
+ }
}
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 3124e185ba..aa2fcc4853 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
@@ -315,6 +315,10 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
testRealmDefaultClientScopes(migrationRealm);
}
+ protected void testMigrationTo19_0_0() {
+ testPostLogoutRedirectUrisSet(migrationRealm);
+ }
+
protected void testDeleteAccount(RealmResource realm) {
ClientRepresentation accountClient = realm.clients().findByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).get(0);
ClientResource accountResource = realm.clients().get(accountClient.getId());
@@ -728,6 +732,11 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
ExportImportUtil.testClientDefaultClientScopes(realm);
}
+ private void testPostLogoutRedirectUrisSet(RealmResource realm) {
+ log.info("Testing that POST_LOGOUT_REDIRECT_URI is set to '+' for all clients in " + realm.toRepresentation().getRealm());
+ ExportImportUtil.testDefaultPostLogoutRedirectUris(realm);
+ }
+
private void testOfflineScopeAddedToClient() {
log.infof("Testing offline_access optional scope present in realm %s for client migration-test-client", migrationRealm.toRepresentation().getRealm());
@@ -941,6 +950,10 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
testMigrationTo18_0_0();
}
+ protected void testMigrationTo19_x() {
+ testMigrationTo19_0_0();
+ }
+
protected void testMigrationTo7_x(boolean supportedAuthzServices) {
if (supportedAuthzServices) {
testDecisionStrategySetOnResourceServer();
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 30056c22c7..1f2c043aa1 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
@@ -77,6 +77,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigratedData(false);
testMigrationTo12_x(true);
testMigrationTo18_x();
+ testMigrationTo19_x();
// Always test offline-token login during migration test
testOfflineTokenLogin();
@@ -95,6 +96,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo9_x();
testMigrationTo12_x(true);
testMigrationTo18_x();
+ testMigrationTo19_x();
// Always test offline-token login during migration test
testOfflineTokenLogin();
@@ -114,6 +116,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo9_x();
testMigrationTo12_x(true);
testMigrationTo18_x();
+ testMigrationTo19_x();
// Always test offline-token login during migration test
testOfflineTokenLogin();
@@ -141,6 +144,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo9_x();
testMigrationTo12_x(false);
testMigrationTo18_x();
+ testMigrationTo19_x();
// Always test offline-token login during migration test
testOfflineTokenLogin();
@@ -161,6 +165,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo9_x();
testMigrationTo12_x(false);
testMigrationTo18_x();
+ testMigrationTo19_x();
// Always test offline-token login during migration test
testOfflineTokenLogin();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java
index 25d343fd95..37a4504f88 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientTokenExchangeTest.java
@@ -189,6 +189,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
directUntrustedPublic.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
directUntrustedPublic.setFullScopeAllowed(false);
directUntrustedPublic.addRedirectUri("*");
+ directUntrustedPublic.setAttribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+");
directUntrustedPublic.addProtocolMapper(AudienceProtocolMapper.createClaimMapper("client-exchanger-audience", clientExchanger.getClientId(), null, true, false));
ClientModel directNoSecret = realm.addClient("direct-no-secret");
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 4737425a9e..6f2dc25b0a 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
@@ -55,7 +55,10 @@ import org.keycloak.testsuite.pages.LoginPage;
import java.io.Closeable;
import java.io.IOException;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import javax.ws.rs.NotFoundException;
@@ -80,6 +83,7 @@ import org.keycloak.testsuite.pages.PageUtils;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
import org.keycloak.testsuite.updaters.UserAttributeUpdater;
+import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.InfinispanTestTimeServiceRule;
import org.keycloak.testsuite.util.Matchers;
import org.keycloak.testsuite.util.OAuthClient;
@@ -165,6 +169,43 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
assertCurrentUrlEquals(redirectUri + "&state=something");
}
+ @Test
+ public void postLogoutRedirect() {
+ OAuthClient.AccessTokenResponse tokenResponse = loginUser();
+ String sessionId = tokenResponse.getSessionState();
+
+ String redirectUri = APP_REDIRECT_URI + "?post_logout";
+
+ List postLogoutRedirectUris = Collections.singletonList(redirectUri);
+ ClientManager.realm(adminClient.realm("test")).clientId("test-app").setPostLogoutRedirectUri(postLogoutRedirectUris);
+
+ String idTokenString = tokenResponse.getIdToken();
+
+ try {
+ String logoutUrl = oauth.getLogoutUrl().postLogoutRedirectUri(redirectUri).idTokenHint(idTokenString).build();
+ driver.navigate().to(logoutUrl);
+
+ events.expectLogout(sessionId).detail(Details.REDIRECT_URI, redirectUri).assertEvent();
+ MatcherAssert.assertThat(false, is(isSessionActive(sessionId)));
+
+ assertCurrentUrlEquals(redirectUri);
+
+ tokenResponse = loginUser();
+ String sessionId2 = tokenResponse.getSessionState();
+ idTokenString = tokenResponse.getIdToken();
+ assertNotEquals(sessionId, sessionId2);
+
+ // Test also "state" parameter is included in the URL after logout. Make sure to use idTokenHint from the last login to match with current browser session
+ logoutUrl = oauth.getLogoutUrl().postLogoutRedirectUri(redirectUri).idTokenHint(idTokenString).state("something").build();
+ driver.navigate().to(logoutUrl);
+ events.expectLogout(sessionId2).detail(Details.REDIRECT_URI, redirectUri).assertEvent();
+ MatcherAssert.assertThat(false, is(isSessionActive(sessionId2)));
+ assertCurrentUrlEquals(redirectUri + "&state=something");
+ } finally {
+ postLogoutRedirectUris = Collections.singletonList("+");
+ ClientManager.realm(adminClient.realm("test")).clientId("test-app").setPostLogoutRedirectUri(postLogoutRedirectUris);
+ }
+ }
@Test
public void logoutRedirectWithIdTokenHintPointToDifferentSession() {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientManager.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientManager.java
index 79cf4a5a80..e4ccd87bb4 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientManager.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientManager.java
@@ -13,6 +13,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
+import java.util.List;
import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId;
import static org.keycloak.testsuite.admin.ApiUtil.findProtocolMapperByName;
@@ -162,6 +163,12 @@ public class ClientManager {
clientResource.update(app);
}
+ public void setPostLogoutRedirectUri(List postLogoutRedirectUris) {
+ ClientRepresentation app = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(app).setPostLogoutRedirectUris(postLogoutRedirectUris);
+ clientResource.update(app);
+ }
+
public ClientManagerBuilder addWebOrigins(String... webOrigins) {
ClientRepresentation app = clientResource.toRepresentation();
if (app.getWebOrigins() == null) {