Support for post_logout_redirect_uris in OIDC client registration (#12282)
Closes #10135
This commit is contained in:
parent
ee0c67c0c8
commit
c00514d659
24 changed files with 263 additions and 2 deletions
|
@ -37,5 +37,9 @@
|
|||
<artifactId>selenium-java</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!--
|
||||
~ * Copyright 2022 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.
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
|
||||
<changeSet author="keycloak" id="19.0.0-10135">
|
||||
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.JpaUpdate19_0_0_DefaultPostLogoutRedirectUri"/>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
|
@ -73,5 +73,6 @@
|
|||
<include file="META-INF/jpa-changelog-15.0.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-17.0.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-18.0.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-19.0.0.xml"/>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -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<String, ClientModel> appMap = new HashMap<String, ClientModel>();
|
||||
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 -> {
|
||||
|
|
|
@ -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<String, ClientModel> 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 -> {
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ClientRepresenta
|
|||
}
|
||||
|
||||
ClientModel client = RepresentationToModel.createClient(session, realm, clientRep);
|
||||
if(OIDCAdvancedConfigWrapper.fromClientModel(client).getPostLogoutRedirectUris() == null) {
|
||||
OIDCAdvancedConfigWrapper.fromClientModel(client).setPostLogoutRedirectUris(Collections.singletonList("+"));
|
||||
}
|
||||
RepresentationToModel.importAuthorizationSettings(clientRep, client, session);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -355,4 +356,27 @@ public class OIDCAdvancedConfigWrapper extends AbstractClientConfigWrapper {
|
|||
setAttribute(ClientModel.TOS_URI, tosUri);
|
||||
}
|
||||
|
||||
public List<String> getPostLogoutRedirectUris() {
|
||||
List<String> 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<String> postLogoutRedirectUris) {
|
||||
setAttributeMultivalued(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, postLogoutRedirectUris);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -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<String> 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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> redirectUris = client.getRedirectUris();
|
||||
if(redirectUris != null && !redirectUris.isEmpty()) {
|
||||
String postLogoutRedirectUris = client.getAttributes().get(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS);
|
||||
Assert.assertEquals("+", postLogoutRedirectUris);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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<String> 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() {
|
||||
|
|
|
@ -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<String> 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) {
|
||||
|
|
Loading…
Reference in a new issue