diff --git a/docs/documentation/upgrading/topics/changes/changes-26_0_0.adoc b/docs/documentation/upgrading/topics/changes/changes-26_0_0.adoc
index b8df6ccebd..8048a9589d 100644
--- a/docs/documentation/upgrading/topics/changes/changes-26_0_0.adoc
+++ b/docs/documentation/upgrading/topics/changes/changes-26_0_0.adoc
@@ -224,3 +224,14 @@ Update your custom embedded Infinispan cache configuration file with configurati
For more details proceed to the https://www.keycloak.org/server/caching[Configuring distributed caches] guide.
+= Support for legacy `redirect_uri` parameter and SPI options has been removed
+
+Previous versions of {project_name} had supported automatic logout of the user and redirecting to the application by opening logout endpoint URL such as
+`http(s)://example-host/auth/realms/my-realm-name/protocol/openid-connect/logout?redirect_uri=encodedRedirectUri`. This functionality was deprecated in {project_name} 18 and has been removed in this version in favor of following the OpenID Connect specification.
+
+As part of this change the following related configuration options for the SPI have been removed:
+
+- `--spi-login-protocol-openid-connect-legacy-logout-redirect-uri`
+- `--spi-login-protocol-openid-connect-suppress-logout-confirmation-screen`
+
+If you were still making use these options or the `redirect_uri` parameter for logout you should implement the link:https://openid.net/specs/openid-connect-rpinitiated-1_0.html[OpenID Connect RP-Initiated Logout specification] instead.
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 689a5f089e..79e0b7d36e 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
@@ -109,21 +109,9 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
public static final String ROLES_SCOPE_CONSENT_TEXT = "${rolesScopeConsentText}";
public static final String ORGANIZATION_SCOPE_CONSENT_TEXT = "${organizationScopeConsentText}";
- public static final String CONFIG_LEGACY_LOGOUT_REDIRECT_URI = "legacy-logout-redirect-uri";
- public static final String SUPPRESS_LOGOUT_CONFIRMATION_SCREEN = "suppress-logout-confirmation-screen";
-
- private OIDCProviderConfig providerConfig;
-
@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);
- }
- if (providerConfig.suppressLogoutConfirmationScreen()) {
- 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.", SUPPRESS_LOGOUT_CONFIRMATION_SCREEN);
- }
}
@Override
@@ -444,7 +432,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
@Override
public Object createProtocolEndpoint(KeycloakSession session, EventBuilder event) {
- return new OIDCLoginProtocolService(session, event, providerConfig);
+ return new OIDCLoginProtocolService(session, event);
}
@Override
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
index a21a706085..968e61a773 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
@@ -64,7 +64,6 @@ public class OIDCLoginProtocolService {
private final RealmModel realm;
private final TokenManager tokenManager;
private final EventBuilder event;
- private final OIDCProviderConfig providerConfig;
private final KeycloakSession session;
@@ -74,13 +73,12 @@ public class OIDCLoginProtocolService {
private final ClientConnection clientConnection;
- public OIDCLoginProtocolService(KeycloakSession session, EventBuilder event, OIDCProviderConfig providerConfig) {
+ public OIDCLoginProtocolService(KeycloakSession session, EventBuilder event) {
this.session = session;
this.clientConnection = session.getContext().getConnection();
this.realm = session.getContext().getRealm();
this.tokenManager = new TokenManager();
this.event = event;
- this.providerConfig = providerConfig;
this.request = session.getContext().getHttpRequest();
this.headers = session.getContext().getRequestHeaders();
}
@@ -212,11 +210,9 @@ public class OIDCLoginProtocolService {
return new UserInfoEndpoint(session, tokenManager);
}
- /* old deprecated logout endpoint needs to be removed in the future
- * https://issues.redhat.com/browse/KEYCLOAK-2940 */
@Path("logout")
public Object logout() {
- return new LogoutEndpoint(session, tokenManager, event, providerConfig);
+ return new LogoutEndpoint(session, tokenManager, event);
}
@Path("revoke")
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCProviderConfig.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCProviderConfig.java
deleted file mode 100644
index b15cf42a37..0000000000
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCProviderConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.
- *
- */
-
-package org.keycloak.protocol.oidc;
-
-import org.keycloak.Config;
-
-/**
- * @author Marek Posolda
- */
-public class OIDCProviderConfig {
-
- private final boolean legacyLogoutRedirectUri;
- private final boolean suppressLogoutConfirmationScreen;
-
- public OIDCProviderConfig(Config.Scope config) {
- this.legacyLogoutRedirectUri = config.getBoolean(OIDCLoginProtocolFactory.CONFIG_LEGACY_LOGOUT_REDIRECT_URI, false);
- this.suppressLogoutConfirmationScreen = config.getBoolean(OIDCLoginProtocolFactory.SUPPRESS_LOGOUT_CONFIRMATION_SCREEN, false);
- }
-
- public boolean isLegacyLogoutRedirectUri() {
- return legacyLogoutRedirectUri;
- }
-
- public boolean suppressLogoutConfirmationScreen() {
- return suppressLogoutConfirmationScreen;
- }
-}
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 a6c1e32885..4512c2c5cd 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,8 +48,6 @@ 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;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.protocol.oidc.utils.LogoutUtil;
@@ -112,17 +110,15 @@ public class LogoutEndpoint {
private final TokenManager tokenManager;
private final RealmModel realm;
private final EventBuilder event;
- private final OIDCProviderConfig providerConfig;
private Cors cors;
- public LogoutEndpoint(KeycloakSession session, TokenManager tokenManager, EventBuilder event, OIDCProviderConfig providerConfig) {
+ public LogoutEndpoint(KeycloakSession session, TokenManager tokenManager, EventBuilder event) {
this.session = session;
this.clientConnection = session.getContext().getConnection();
this.tokenManager = tokenManager;
this.realm = session.getContext().getRealm();
this.event = event;
- this.providerConfig = providerConfig;
this.request = session.getContext().getHttpRequest();
this.headers = session.getContext().getRequestHeaders();
}
@@ -143,7 +139,6 @@ public class LogoutEndpoint {
*
* All parameters are optional. Some combinations of parameters are invalid as described in the specification
*
- * @param deprecatedRedirectUri Parameter "redirect_uri" is not supported by the specification. It is here just for the backwards compatibility
* @param encodedIdToken Parameter "id_token_hint" as described in the specification.
* @param clientId Parameter "client_id" as described in the specification.
* @param postLogoutRedirectUri Parameter "post_logout_redirect_uri" as described in the specification with the URL to redirect after logout.
@@ -154,39 +149,23 @@ public class LogoutEndpoint {
*/
@GET
@NoCache
- public Response logout(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String deprecatedRedirectUri, // deprecated
- @QueryParam(OIDCLoginProtocol.ID_TOKEN_HINT) String encodedIdToken,
+ public Response logout(@QueryParam(OIDCLoginProtocol.ID_TOKEN_HINT) String encodedIdToken,
@QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId,
@QueryParam(OIDCLoginProtocol.POST_LOGOUT_REDIRECT_URI_PARAM) String postLogoutRedirectUri,
@QueryParam(OIDCLoginProtocol.STATE_PARAM) String state,
@QueryParam(OIDCLoginProtocol.UI_LOCALES_PARAM) String uiLocales,
@QueryParam(AuthenticationManager.INITIATING_IDP_PARAM) String initiatingIdp) {
- if (!providerConfig.isLegacyLogoutRedirectUri()) {
- if (deprecatedRedirectUri != null) {
- event.event(EventType.LOGOUT);
- String errorMessage = "Parameter 'redirect_uri' no longer supported.";
- event.detail(Details.REASON, errorMessage);
- event.error(Errors.INVALID_REQUEST);
- logger.warnf("%s Please use 'post_logout_redirect_uri' with 'id_token_hint' for this endpoint. Alternatively you can enable backwards compatibility option '%s' of oidc login protocol in the server configuration.",
- errorMessage, OIDCLoginProtocolFactory.CONFIG_LEGACY_LOGOUT_REDIRECT_URI);
- return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM);
- }
-
- if (postLogoutRedirectUri != null && encodedIdToken == null && clientId == null) {
- event.event(EventType.LOGOUT);
- String errorMessage = "Either the parameter 'client_id' or the parameter 'id_token_hint' is required when 'post_logout_redirect_uri' is used.";
- event.detail(Details.REASON, errorMessage);
- event.error(Errors.INVALID_REQUEST);
- logger.warnf(errorMessage);
- return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.MISSING_PARAMETER,
- OIDCLoginProtocol.ID_TOKEN_HINT);
- }
+ if (postLogoutRedirectUri != null && encodedIdToken == null && clientId == null) {
+ event.event(EventType.LOGOUT);
+ String errorMessage = "Either the parameter 'client_id' or the parameter 'id_token_hint' is required when 'post_logout_redirect_uri' is used.";
+ event.detail(Details.REASON, errorMessage);
+ event.error(Errors.INVALID_REQUEST);
+ logger.warnf(errorMessage);
+ return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.MISSING_PARAMETER,
+ OIDCLoginProtocol.ID_TOKEN_HINT);
}
- deprecatedRedirectUri = providerConfig.isLegacyLogoutRedirectUri() ? deprecatedRedirectUri : null;
- final String redirectUri = postLogoutRedirectUri != null ? postLogoutRedirectUri : deprecatedRedirectUri;
-
boolean confirmationNeeded = true;
boolean forcedConfirmation = false;
ClientModel client = clientId == null ? null : realm.getClientByClientId(clientId);
@@ -236,21 +215,16 @@ public class LogoutEndpoint {
}
String validatedRedirectUri = null;
- if (redirectUri != null) {
+ if (postLogoutRedirectUri != null) {
if (client != null) {
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 && providerConfig.isLegacyLogoutRedirectUri()) {
- /*
- * 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);
+ validatedRedirectUri = RedirectUtils.verifyRedirectUri(session, client.getRootUrl(), postLogoutRedirectUri, postLogoutRedirectUris, true);
}
if (validatedRedirectUri == null) {
event.event(EventType.LOGOUT);
- event.detail(Details.REDIRECT_URI, redirectUri);
+ event.detail(Details.REDIRECT_URI, postLogoutRedirectUri);
event.error(Errors.INVALID_REDIRECT_URI);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REDIRECT_URI);
}
@@ -307,7 +281,7 @@ public class LogoutEndpoint {
}
// Logout confirmation screen will be displayed to the user in this case
- if ((confirmationNeeded || forcedConfirmation) && !providerConfig.suppressLogoutConfirmationScreen()) {
+ if (confirmationNeeded || forcedConfirmation) {
return displayLogoutConfirmationScreen(loginForm, logoutSession);
} else {
return doBrowserLogout(logoutSession);
@@ -338,13 +312,14 @@ public class LogoutEndpoint {
if (form.containsKey(OAuth2Constants.REFRESH_TOKEN)) {
return logoutToken();
} else {
- return logout(form.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM),
+ return logout(
form.getFirst(OIDCLoginProtocol.ID_TOKEN_HINT),
form.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM),
form.getFirst(OIDCLoginProtocol.POST_LOGOUT_REDIRECT_URI_PARAM),
form.getFirst(OIDCLoginProtocol.STATE_PARAM),
form.getFirst(OIDCLoginProtocol.UI_LOCALES_PARAM),
- form.getFirst(AuthenticationManager.INITIATING_IDP_PARAM));
+ form.getFirst(AuthenticationManager.INITIATING_IDP_PARAM)
+ );
}
}
@@ -701,7 +676,7 @@ public class LogoutEndpoint {
return backchannelLogoutResponse;
}
-
+
private boolean oneOrMoreDownstreamLogoutsFailed(BackchannelLogoutResponse backchannelLogoutResponse) {
BackchannelLogoutResponse filteredBackchannelLogoutResponse = new BackchannelLogoutResponse();
for (BackchannelLogoutResponse.DownStreamBackchannelLogoutResponse response : backchannelLogoutResponse
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 de8fd84e0a..500cc2bb80 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
@@ -24,7 +24,6 @@ import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakUriInfo;
import org.keycloak.models.RealmModel;
-import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.services.Urls;
import org.keycloak.services.util.ResolveRelative;
@@ -33,7 +32,6 @@ import java.util.Collection;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
-import java.util.stream.Collectors;
/**
* @author Stian Thorgersen
@@ -42,17 +40,6 @@ public class RedirectUtils {
private static final Logger logger = Logger.getLogger(RedirectUtils.class);
- /**
- * This method is deprecated for performance and security reasons and it is available just for the
- * backwards compatibility. It is recommended to use some other methods of this class where the client is given as an argument
- * to the method, so we know the client, which redirect-uri we are trying to resolve.
- */
- @Deprecated
- public static String verifyRealmRedirectUri(KeycloakSession session, String redirectUri) {
- Set validRedirects = getValidateRedirectUris(session);
- return verifyRedirectUri(session, null, redirectUri, validRedirects, true);
- }
-
public static String verifyRedirectUri(KeycloakSession session, String redirectUri, ClientModel client) {
return verifyRedirectUri(session, redirectUri, client, true);
}
@@ -77,16 +64,6 @@ public class RedirectUtils {
return resolveValidRedirects;
}
- @Deprecated
- private static Set getValidateRedirectUris(KeycloakSession session) {
- RealmModel realm = session.getContext().getRealm();
- return session.clients().getAllRedirectUrisOfEnabledClients(realm).entrySet().stream()
- .filter(me -> me.getKey().isEnabled() && OIDCLoginProtocol.LOGIN_PROTOCOL.equals(me.getKey().getProtocol()) && !me.getKey().isBearerOnly() && (me.getKey().isStandardFlowEnabled() || me.getKey().isImplicitFlowEnabled()))
- .map(me -> resolveValidRedirects(session, me.getKey().getRootUrl(), me.getValue()))
- .flatMap(Collection::stream)
- .collect(Collectors.toSet());
- }
-
public static String verifyRedirectUri(KeycloakSession session, String rootUrl, String redirectUri, Set validRedirects, boolean requireRedirectUri) {
KeycloakUriInfo uriInfo = session.getContext().getUri();
RealmModel realm = session.getContext().getRealm();
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
index 046bbdf3f5..a07a5e8293 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
@@ -232,14 +232,6 @@ public class OAuthClient {
return this;
}
- @Deprecated // Use only in backwards compatibility tests
- public LogoutUrlBuilder redirectUri(String redirectUri) {
- if (redirectUri != null) {
- b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
- }
- return this;
- }
-
public LogoutUrlBuilder state(String state) {
if (state != null) {
b.queryParam(OIDCLoginProtocol.STATE_PARAM, state);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java
index bf65998d30..1a53300d44 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java
@@ -1,5 +1,6 @@
package org.keycloak.testsuite.broker;
+import org.keycloak.OAuth2Constants;
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
import org.keycloak.crypto.Algorithm;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
@@ -21,6 +22,7 @@ import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater;
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
import org.keycloak.testsuite.util.KeyUtils;
+import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.SamlClient;
import org.keycloak.testsuite.util.SamlClient.Binding;
import org.keycloak.testsuite.util.SamlClientBuilder;
@@ -148,8 +150,14 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
loginUser();
// Logout should fail because logout response is not signed.
+ final String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ final OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+ final String idTokenString = tokenResponse.getIdToken();
final String redirectUri = getAccountUrl(getProviderRoot(), bc.providerRealmName());
- final String logoutUri = oauth.realm(bc.providerRealmName()).getLogoutUrl().redirectUri(redirectUri).build();
+ final String logoutUri = oauth.realm(bc.providerRealmName()).getLogoutUrl()
+ .idTokenHint(idTokenString)
+ .postLogoutRedirectUri(redirectUri).build();
+
driver.navigate().to(logoutUri);
errorPage.assertCurrent();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LegacyLogoutTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LegacyLogoutTest.java
deleted file mode 100644
index 3b71751ee9..0000000000
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LegacyLogoutTest.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * 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.
- *
- */
-
-package org.keycloak.testsuite.oauth;
-
-import java.io.Closeable;
-import java.util.Collections;
-
-import jakarta.ws.rs.NotFoundException;
-
-import org.hamcrest.MatcherAssert;
-import org.jboss.arquillian.graphene.page.Page;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.keycloak.OAuth2Constants;
-import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.admin.client.resource.ClientsResource;
-import org.keycloak.events.Details;
-import org.keycloak.events.Errors;
-import org.keycloak.protocol.LoginProtocol;
-import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
-import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
-import org.keycloak.testsuite.Assert;
-import org.keycloak.testsuite.AssertEvents;
-import org.keycloak.testsuite.admin.ApiUtil;
-import org.keycloak.testsuite.pages.AppPage;
-import org.keycloak.testsuite.pages.ErrorPage;
-import org.keycloak.testsuite.pages.InfoPage;
-import org.keycloak.testsuite.pages.LoginPage;
-import org.keycloak.testsuite.pages.LogoutConfirmPage;
-import org.keycloak.testsuite.pages.OAuthGrantPage;
-import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
-import org.keycloak.testsuite.util.InfinispanTestTimeServiceRule;
-import org.keycloak.testsuite.util.OAuthClient;
-import org.keycloak.testsuite.util.ServerURLs;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
-
-/**
- * Test logout endpoint with deprecated "redirect_uri" parameter
- *
- * @author Marek Posolda
- */
-public class LegacyLogoutTest extends AbstractTestRealmKeycloakTest {
-
- @Rule
- public AssertEvents events = new AssertEvents(this);
-
- @Rule
- public InfinispanTestTimeServiceRule ispnTestTimeService = new InfinispanTestTimeServiceRule(this);
-
- @Page
- protected AppPage appPage;
-
- @Page
- protected LoginPage loginPage;
-
- @Page
- protected OAuthGrantPage grantPage;
-
- @Page
- protected LogoutConfirmPage logoutConfirmPage;
-
- @Page
- protected InfoPage infoPage;
-
- @Page
- private ErrorPage errorPage;
-
- private String APP_REDIRECT_URI;
-
- @Override
- public void configureTestRealm(RealmRepresentation testRealm) {
- }
-
- @Before
- public void configLegacyRedirectUriEnabled() {
- getTestingClient().testing().setSystemPropertyOnServer("oidc." + OIDCLoginProtocolFactory.CONFIG_LEGACY_LOGOUT_REDIRECT_URI, "true");
- getTestingClient().testing().reinitializeProviderFactoryWithSystemPropertiesScope(LoginProtocol.class.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL, "oidc.");
-
- APP_REDIRECT_URI = oauth.APP_AUTH_ROOT;
- }
-
- @After
- public void revertConfiguration() {
- getTestingClient().testing().setSystemPropertyOnServer("oidc." + OIDCLoginProtocolFactory.CONFIG_LEGACY_LOGOUT_REDIRECT_URI, "false");
- getTestingClient().testing().setSystemPropertyOnServer("oidc." + OIDCLoginProtocolFactory.SUPPRESS_LOGOUT_CONFIRMATION_SCREEN, "false");
- getTestingClient().testing().reinitializeProviderFactoryWithSystemPropertiesScope(LoginProtocol.class.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL, "oidc.");
- }
-
-
- // Test logout with deprecated "redirect_uri" and with "id_token_hint" . Should od automatic redirect
- @Test
- public void logoutWithLegacyRedirectUriAndIdTokenHint() throws Exception {
- OAuthClient.AccessTokenResponse tokenResponse = loginUser();
- String idTokenString = tokenResponse.getIdToken();
- String sessionId = tokenResponse.getSessionState();
-
- String logoutUrl = oauth.getLogoutUrl().redirectUri(APP_REDIRECT_URI).idTokenHint(idTokenString).build();
- driver.navigate().to(logoutUrl);
-
- events.expectLogout(sessionId).detail(Details.REDIRECT_URI, APP_REDIRECT_URI).assertEvent();
- assertThat(false, is(isSessionActive(sessionId)));
- assertCurrentUrlEquals(APP_REDIRECT_URI);
- }
-
- // Test logout with deprecated "redirect_uri" and without "id_token_hint" . User should confirm logout
- @Test
- public void logoutWithLegacyRedirectUriAndWithoutIdTokenHint() throws Exception {
- OAuthClient.AccessTokenResponse tokenResponse = loginUser();
- String sessionId = tokenResponse.getSessionState();
-
- String logoutUrl = oauth.getLogoutUrl().redirectUri(APP_REDIRECT_URI).build();
- driver.navigate().to(logoutUrl);
-
- // Assert logout confirmation page. Session still exists. Assert default language on logout page (English)
- logoutConfirmPage.assertCurrent();
- assertThat(true, is(isSessionActive(sessionId)));
- events.assertEmpty();
- logoutConfirmPage.confirmLogout();
-
- // Redirected back to the application with expected state
- events.expectLogout(sessionId).client("account").removeDetail(Details.REDIRECT_URI).assertEvent();
- assertThat(false, is(isSessionActive(sessionId)));
- assertCurrentUrlEquals(APP_REDIRECT_URI);
- }
-
- // Test with "post_logout_redirect_uri" without "id_token_hint": User should confirm logout.
- @Test
- public void logoutWithPostLogoutUriWithoutIdTokenHint() {
- OAuthClient.AccessTokenResponse tokenResponse = loginUser();
- String sessionId = tokenResponse.getSessionState();
-
- String logoutUrl = oauth.getLogoutUrl().postLogoutRedirectUri(APP_REDIRECT_URI).build();
- driver.navigate().to(logoutUrl);
-
- // Assert logout confirmation page. Session still exists. Assert default language on logout page (English)
- logoutConfirmPage.assertCurrent();
- assertThat(true, is(isSessionActive(sessionId)));
- events.assertEmpty();
- logoutConfirmPage.confirmLogout();
-
- // Redirected back to the application with expected state
- events.expectLogout(sessionId).client("account").removeDetail(Details.REDIRECT_URI).assertEvent();
- assertThat(false, is(isSessionActive(sessionId)));
- assertCurrentUrlEquals(APP_REDIRECT_URI);
- }
-
-
- // KEYCLOAK-16517 Make sure that just real clients with standardFlow or implicitFlow enabled are considered for redirectUri
- @Test
- public void logoutRedirectWithStarRedirectUriForDirectGrantClient() {
- // Set "*" as redirectUri for some directGrant client
- ClientResource clientRes = ApiUtil.findClientByClientId(testRealm(), "direct-grant");
- ClientRepresentation clientRepOrig = clientRes.toRepresentation();
- ClientRepresentation clientRep = clientRes.toRepresentation();
- clientRep.setStandardFlowEnabled(false);
- clientRep.setImplicitFlowEnabled(false);
- clientRep.setRedirectUris(Collections.singletonList("*"));
- clientRes.update(clientRep);
-
- try {
- OAuthClient.AccessTokenResponse tokenResponse = loginUser();
-
- String invalidRedirectUri = ServerURLs.getAuthServerContextRoot() + "/bar";
-
- String idTokenString = tokenResponse.getIdToken();
-
- String logoutUrl = oauth.getLogoutUrl().redirectUri(invalidRedirectUri).build();
- driver.navigate().to(logoutUrl);
-
- events.expectLogoutError(Errors.INVALID_REDIRECT_URI).assertEvent();
-
- assertCurrentUrlDoesntStartWith(invalidRedirectUri);
- errorPage.assertCurrent();
- Assert.assertEquals("Invalid redirect uri", errorPage.getError());
-
- // Session still active
- assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
- } finally {
- // Revert
- clientRes.update(clientRepOrig);
- }
- }
-
- // Test logout with deprecated "redirect_uri" and without "id_token_hint" and client disabled after login
- @Test
- public void logoutWithLegacyRedirectUriAndWithoutIdTokenHintClientDisabled() throws Exception {
- OAuthClient.AccessTokenResponse tokenResponse = loginUser();
- String sessionId = tokenResponse.getSessionState();
-
- try (Closeable testAppClient = ClientAttributeUpdater.forClient(adminClient, "test", oauth.getClientId())
- .setEnabled(false).update()) {
-
- ClientsResource clients = adminClient.realm(oauth.getRealm()).clients();
- ClientRepresentation rep = clients.findByClientId(oauth.getClientId()).get(0);
- MatcherAssert.assertThat(false, is(rep.isEnabled()));
-
- String logoutUrl = oauth.getLogoutUrl().redirectUri(APP_REDIRECT_URI).build();
- driver.navigate().to(logoutUrl);
-
- // Assert logout confirmation page. Session still exists. Assert default language on logout page (English)
- logoutConfirmPage.assertCurrent();
- MatcherAssert.assertThat(true, is(isSessionActive(sessionId)));
- events.assertEmpty();
- logoutConfirmPage.confirmLogout();
-
- // Redirected back to the application with expected state
- events.expectLogout(sessionId).client("account").removeDetail(Details.REDIRECT_URI).assertEvent();
- MatcherAssert.assertThat(false, is(isSessionActive(sessionId)));
- assertCurrentUrlEquals(APP_REDIRECT_URI);
- }
- }
-
- // Test with "post_logout_redirect_uri" without "id_token_hint" and "suppress-logout-confirmation-screen": User should logout non interactive.
- @Test
- public void logoutWithPostLogoutUriWithoutIdTokenHintAndSuppressedConfirmation() {
- getTestingClient().testing().setSystemPropertyOnServer("oidc." + OIDCLoginProtocolFactory.SUPPRESS_LOGOUT_CONFIRMATION_SCREEN, "true");
- getTestingClient().testing().reinitializeProviderFactoryWithSystemPropertiesScope(LoginProtocol.class.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL, "oidc.");
-
- OAuthClient.AccessTokenResponse tokenResponse = loginUser();
- String sessionId = tokenResponse.getSessionState();
-
- String logoutUrl = oauth.getLogoutUrl().postLogoutRedirectUri(APP_REDIRECT_URI).build();
- driver.navigate().to(logoutUrl);
-
- events.expectLogout(sessionId).client("account").detail(Details.REDIRECT_URI, APP_REDIRECT_URI).assertEvent();
- assertThat(false, is(isSessionActive(sessionId)));
- assertCurrentUrlEquals(APP_REDIRECT_URI);
- }
-
- private OAuthClient.AccessTokenResponse loginUser() {
- oauth.doLogin("test-user@localhost", "password");
- String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
-
- oauth.clientSessionState("client-session");
- OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
- events.clear();
- return tokenResponse;
- }
-
- private boolean isSessionActive(String sessionId) {
- try {
- testingClient.testing().getClientSessionsCountInUserSession("test", sessionId);
- return true;
- } catch (NotFoundException nfe) {
- return false;
- }
- }
-}
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 e5b9bfe16c..fdc54426a9 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
@@ -403,29 +403,6 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
}
- // Parameter "redirect_uri" is not valid in logoutRequest (See LegacyLogoutTest for the scenario with "redirect_uri" allowed by backwards compatibility switch)
- @Test
- public void logoutWithRedirectUriParameterShouldFail() throws Exception {
- OAuthClient.AccessTokenResponse tokenResponse = loginUser();
- String idTokenString = tokenResponse.getIdToken();
-
- // Logout with "redirect_uri" parameter alone should fail
- String logoutUrl = oauth.getLogoutUrl().redirectUri(APP_REDIRECT_URI).build();
- driver.navigate().to(logoutUrl);
- errorPage.assertCurrent();
- events.expectLogoutError(OAuthErrorException.INVALID_REQUEST).assertEvent();
-
- // Logout with "redirect_uri" parameter and with "id_token_hint" should fail
- oauth.getLogoutUrl().idTokenHint(idTokenString).redirectUri(APP_REDIRECT_URI).build();
- driver.navigate().to(logoutUrl);
- errorPage.assertCurrent();
- events.expectLogoutError(OAuthErrorException.INVALID_REQUEST).assertEvent();
-
- // Assert user still authenticated
- MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
- }
-
-
// Test with "post_logout_redirect_uri" without "id_token_hint" should fail
@Test
public void logoutWithPostLogoutUriWithoutIdTokenHintShouldFail() throws Exception {
diff --git a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json
index fd458cd227..04a94c75f5 100755
--- a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json
+++ b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json
@@ -148,9 +148,6 @@
},
"login-protocol": {
- "openid-connect": {
- "legacy-logout-redirect-uri": "${keycloak.oidc.legacyLogoutRedirectUri:false}"
- },
"saml": {
"knownProtocols": [
"http=${auth.server.http.port}",