KEYCLOAK-7166 Added the possibility of not logging out of remote idp on browser logout, by passing a query param containing the id of the identity provider
This commit is contained in:
parent
5880efe775
commit
36b0d8b80e
5 changed files with 114 additions and 8 deletions
|
@ -88,7 +88,11 @@ public class LogoutEndpoint {
|
||||||
/**
|
/**
|
||||||
* Logout user session. User must be logged in via a session cookie.
|
* Logout user session. User must be logged in via a session cookie.
|
||||||
*
|
*
|
||||||
|
* When the logout is initiated by a remote idp, the parameter "initiating_idp" can be supplied. This param will
|
||||||
|
* prevent upstream logout (since the logout procedure has already been started in the remote idp).
|
||||||
|
*
|
||||||
* @param redirectUri
|
* @param redirectUri
|
||||||
|
* @param initiatingIdp The alias of the idp initiating the logout.
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
|
@ -96,7 +100,8 @@ public class LogoutEndpoint {
|
||||||
public Response logout(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri, // deprecated
|
public Response logout(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri, // deprecated
|
||||||
@QueryParam("id_token_hint") String encodedIdToken,
|
@QueryParam("id_token_hint") String encodedIdToken,
|
||||||
@QueryParam("post_logout_redirect_uri") String postLogoutRedirectUri,
|
@QueryParam("post_logout_redirect_uri") String postLogoutRedirectUri,
|
||||||
@QueryParam("state") String state) {
|
@QueryParam("state") String state,
|
||||||
|
@QueryParam("initiating_idp") String initiatingIdp) {
|
||||||
String redirect = postLogoutRedirectUri != null ? postLogoutRedirectUri : redirectUri;
|
String redirect = postLogoutRedirectUri != null ? postLogoutRedirectUri : redirectUri;
|
||||||
|
|
||||||
if (redirect != null) {
|
if (redirect != null) {
|
||||||
|
@ -130,7 +135,7 @@ public class LogoutEndpoint {
|
||||||
if (state != null) userSession.setNote(OIDCLoginProtocol.LOGOUT_STATE_PARAM, state);
|
if (state != null) userSession.setNote(OIDCLoginProtocol.LOGOUT_STATE_PARAM, state);
|
||||||
userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, OIDCLoginProtocol.LOGIN_PROTOCOL);
|
userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
logger.debug("Initiating OIDC browser logout");
|
logger.debug("Initiating OIDC browser logout");
|
||||||
Response response = AuthenticationManager.browserLogout(session, realm, authResult.getSession(), session.getContext().getUri(), clientConnection, headers);
|
Response response = AuthenticationManager.browserLogout(session, realm, authResult.getSession(), session.getContext().getUri(), clientConnection, headers, initiatingIdp);
|
||||||
logger.debug("finishing OIDC browser logout");
|
logger.debug("finishing OIDC browser logout");
|
||||||
return response;
|
return response;
|
||||||
} else if (userSession != null) { // non browser logout
|
} else if (userSession != null) { // non browser logout
|
||||||
|
|
|
@ -184,7 +184,7 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
|
|
||||||
session.getContext().setClient(client);
|
session.getContext().setClient(client);
|
||||||
logger.debug("logout response");
|
logger.debug("logout response");
|
||||||
Response response = authManager.browserLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers);
|
Response response = authManager.browserLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, null);
|
||||||
event.success();
|
event.success();
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
@ -422,7 +422,7 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
|
clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
|
||||||
}
|
}
|
||||||
logger.debug("browser Logout");
|
logger.debug("browser Logout");
|
||||||
return authManager.browserLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers);
|
return authManager.browserLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, null);
|
||||||
} else if (logoutRequest.getSessionIndex() != null) {
|
} else if (logoutRequest.getSessionIndex() != null) {
|
||||||
for (String sessionIndex : logoutRequest.getSessionIndex()) {
|
for (String sessionIndex : logoutRequest.getSessionIndex()) {
|
||||||
|
|
||||||
|
|
|
@ -482,14 +482,20 @@ public class AuthenticationManager {
|
||||||
for (UserSessionModel userSession : userSessions) {
|
for (UserSessionModel userSession : userSessions) {
|
||||||
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
||||||
if (clientSession != null) {
|
if (clientSession != null) {
|
||||||
AuthenticationManager.backchannelLogoutClientSession(session, realm, clientSession, null, uriInfo, headers);
|
backchannelLogoutClientSession(session, realm, clientSession, null, uriInfo, headers);
|
||||||
clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
|
clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
|
||||||
org.keycloak.protocol.oidc.TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
|
org.keycloak.protocol.oidc.TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Response browserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
|
public static Response browserLogout(KeycloakSession session,
|
||||||
|
RealmModel realm,
|
||||||
|
UserSessionModel userSession,
|
||||||
|
UriInfo uriInfo,
|
||||||
|
ClientConnection connection,
|
||||||
|
HttpHeaders headers,
|
||||||
|
String initiatingIdp) {
|
||||||
if (userSession == null) return null;
|
if (userSession == null) return null;
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
|
@ -510,7 +516,7 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
String brokerId = userSession.getNote(Details.IDENTITY_PROVIDER);
|
String brokerId = userSession.getNote(Details.IDENTITY_PROVIDER);
|
||||||
if (brokerId != null) {
|
if (brokerId != null && !brokerId.equals(initiatingIdp)) {
|
||||||
IdentityProvider identityProvider = IdentityBrokerService.getIdentityProvider(session, realm, brokerId);
|
IdentityProvider identityProvider = IdentityBrokerService.getIdentityProvider(session, realm, brokerId);
|
||||||
response = identityProvider.keycloakInitiatedBrowserLogout(session, userSession, uriInfo, realm);
|
response = identityProvider.keycloakInitiatedBrowserLogout(session, userSession, uriInfo, realm);
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.testsuite.broker;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
@ -163,10 +164,16 @@ public abstract class AbstractBaseBrokerTest extends AbstractKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void logoutFromRealm(String realm) {
|
protected void logoutFromRealm(String realm) {
|
||||||
|
logoutFromRealm(realm, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void logoutFromRealm(String realm, String initiatingIdp) {
|
||||||
driver.navigate().to(BrokerTestTools.getAuthRoot(suiteContext)
|
driver.navigate().to(BrokerTestTools.getAuthRoot(suiteContext)
|
||||||
+ "/auth/realms/" + realm
|
+ "/auth/realms/" + realm
|
||||||
+ "/protocol/" + "openid-connect"
|
+ "/protocol/" + "openid-connect"
|
||||||
+ "/logout?redirect_uri=" + encodeUrl(getAccountUrl(realm)));
|
+ "/logout?redirect_uri=" + encodeUrl(getAccountUrl(realm))
|
||||||
|
+ (!StringUtils.isBlank(initiatingIdp) ? "&initiating_idp=" + initiatingIdp : "")
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Retry.execute(() -> {
|
Retry.execute(() -> {
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
package org.keycloak.testsuite.broker;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient;
|
||||||
|
import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword;
|
||||||
|
import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_PROV_NAME;
|
||||||
|
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
||||||
|
|
||||||
|
public class KcOidcBrokerLogoutTest extends AbstractBaseBrokerTest {
|
||||||
|
@Override
|
||||||
|
protected BrokerConfiguration getBrokerConfiguration() {
|
||||||
|
return KcOidcBrokerConfiguration.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void createUser() {
|
||||||
|
log.debug("creating user for realm " + bc.providerRealmName());
|
||||||
|
|
||||||
|
final UserRepresentation user = new UserRepresentation();
|
||||||
|
user.setUsername(bc.getUserLogin());
|
||||||
|
user.setEmail(bc.getUserEmail());
|
||||||
|
user.setEmailVerified(true);
|
||||||
|
user.setEnabled(true);
|
||||||
|
|
||||||
|
final RealmResource realmResource = adminClient.realm(bc.providerRealmName());
|
||||||
|
final String userId = createUserWithAdminClient(realmResource, user);
|
||||||
|
|
||||||
|
resetUserPassword(realmResource.users().get(userId), bc.getUserPassword(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void addIdentityProviderToProviderRealm() {
|
||||||
|
log.debug("adding identity provider to realm " + bc.consumerRealmName());
|
||||||
|
|
||||||
|
final RealmResource realm = adminClient.realm(bc.consumerRealmName());
|
||||||
|
realm.identityProviders().create(bc.setUpIdentityProvider(suiteContext)).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void addClients() {
|
||||||
|
final List<ClientRepresentation> clients = bc.createProviderClients(suiteContext);
|
||||||
|
final RealmResource providerRealm = adminClient.realm(bc.providerRealmName());
|
||||||
|
for (final ClientRepresentation client : clients) {
|
||||||
|
log.debug("adding client " + client.getClientId() + " to realm " + bc.providerRealmName());
|
||||||
|
|
||||||
|
final Response resp = providerRealm.clients().create(client);
|
||||||
|
resp.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void logoutWithoutInitiatingIdpLogsOutOfIdp() {
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
assertLoggedInAccountManagement();
|
||||||
|
|
||||||
|
logoutFromRealm(bc.consumerRealmName());
|
||||||
|
driver.navigate().to(getAccountUrl(REALM_PROV_NAME));
|
||||||
|
waitForPage(driver, "log in to provider", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void logoutWithActualIdpAsInitiatingIdpDoesNotLogOutOfIdp() {
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
assertLoggedInAccountManagement();
|
||||||
|
|
||||||
|
logoutFromRealm(bc.consumerRealmName(), "kc-oidc-idp");
|
||||||
|
driver.navigate().to(getAccountUrl(REALM_PROV_NAME));
|
||||||
|
waitForPage(driver, "keycloak account management", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void logoutWithOtherIdpAsInitiatinIdpLogsOutOfIdp() {
|
||||||
|
logInAsUserInIDPForFirstTime();
|
||||||
|
assertLoggedInAccountManagement();
|
||||||
|
|
||||||
|
logoutFromRealm(bc.consumerRealmName(), "something-else");
|
||||||
|
driver.navigate().to(getAccountUrl(REALM_PROV_NAME));
|
||||||
|
waitForPage(driver, "log in to provider", true);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue