parent
ec808d28bb
commit
e44cea587f
5 changed files with 104 additions and 17 deletions
|
@ -42,7 +42,7 @@ import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||||
public class LogoutSessionCodeChecks extends SessionCodeChecks {
|
public class LogoutSessionCodeChecks extends SessionCodeChecks {
|
||||||
|
|
||||||
public LogoutSessionCodeChecks(RealmModel realm, UriInfo uriInfo, HttpRequest request, ClientConnection clientConnection, KeycloakSession session, EventBuilder event,
|
public LogoutSessionCodeChecks(RealmModel realm, UriInfo uriInfo, HttpRequest request, ClientConnection clientConnection, KeycloakSession session, EventBuilder event,
|
||||||
String code, String clientId, String tabId) {
|
String code, String clientId, String tabId) {
|
||||||
super(realm, uriInfo, request, clientConnection, session, event, null, code, null, clientId, tabId, null);
|
super(realm, uriInfo, request, clientConnection, session, event, null, code, null, clientId, tabId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,4 +67,9 @@ public class LogoutSessionCodeChecks extends SessionCodeChecks {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean checkClientDisabled(ClientModel client) {
|
||||||
|
return !client.isEnabled() && getClientCode() != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.services.resources;
|
package org.keycloak.services.resources;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
import javax.ws.rs.core.Cookie;
|
import javax.ws.rs.core.Cookie;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
@ -151,7 +152,8 @@ public class SessionCodeChecks {
|
||||||
// object retrieve
|
// object retrieve
|
||||||
AuthenticationSessionManager authSessionManager = new AuthenticationSessionManager(session);
|
AuthenticationSessionManager authSessionManager = new AuthenticationSessionManager(session);
|
||||||
AuthenticationSessionModel authSession = null;
|
AuthenticationSessionModel authSession = null;
|
||||||
if (authSessionId != null) authSession = authSessionManager.getAuthenticationSessionByIdAndClient(realm, authSessionId, client, tabId);
|
if (authSessionId != null)
|
||||||
|
authSession = authSessionManager.getAuthenticationSessionByIdAndClient(realm, authSessionId, client, tabId);
|
||||||
AuthenticationSessionModel authSessionCookie = authSessionManager.getCurrentAuthenticationSession(realm, client, tabId);
|
AuthenticationSessionModel authSessionCookie = authSessionManager.getCurrentAuthenticationSession(realm, client, tabId);
|
||||||
|
|
||||||
if (authSession != null && authSessionCookie != null && !authSession.getParentSession().getId().equals(authSessionCookie.getParentSession().getId())) {
|
if (authSession != null && authSessionCookie != null && !authSession.getParentSession().getId().equals(authSessionCookie.getParentSession().getId())) {
|
||||||
|
@ -222,9 +224,9 @@ public class SessionCodeChecks {
|
||||||
setClientToEvent(client);
|
setClientToEvent(client);
|
||||||
session.getContext().setClient(client);
|
session.getContext().setClient(client);
|
||||||
|
|
||||||
if (!client.isEnabled()) {
|
if (checkClientDisabled(client)) {
|
||||||
event.error(Errors.CLIENT_DISABLED);
|
event.error(Errors.CLIENT_DISABLED);
|
||||||
response = ErrorPage.error(session,authSession, Response.Status.BAD_REQUEST, Messages.LOGIN_REQUESTER_NOT_ENABLED);
|
response = ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.LOGIN_REQUESTER_NOT_ENABLED);
|
||||||
clientCode.removeExpiredClientSession();
|
clientCode.removeExpiredClientSession();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -236,7 +238,7 @@ public class SessionCodeChecks {
|
||||||
String lastFlow = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
|
String lastFlow = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
|
||||||
|
|
||||||
// Check if we transitted between flows (eg. clicking "register" on login screen)
|
// Check if we transitted between flows (eg. clicking "register" on login screen)
|
||||||
if (execution==null && !flowPath.equals(lastFlow)) {
|
if (execution == null && !flowPath.equals(lastFlow)) {
|
||||||
logger.debugf("Transition between flows! Current flow: %s, Previous flow: %s", flowPath, lastFlow);
|
logger.debugf("Transition between flows! Current flow: %s, Previous flow: %s", flowPath, lastFlow);
|
||||||
|
|
||||||
// Don't allow moving to different flow if I am on requiredActions already
|
// Don't allow moving to different flow if I am on requiredActions already
|
||||||
|
@ -378,7 +380,7 @@ public class SessionCodeChecks {
|
||||||
AuthenticationSessionModel authSession = null;
|
AuthenticationSessionModel authSession = null;
|
||||||
|
|
||||||
Cookie cook = RestartLoginCookie.getRestartCookie(session);
|
Cookie cook = RestartLoginCookie.getRestartCookie(session);
|
||||||
if(cook == null){
|
if (cook == null) {
|
||||||
event.error(Errors.COOKIE_NOT_FOUND);
|
event.error(Errors.COOKIE_NOT_FOUND);
|
||||||
return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.COOKIE_NOT_FOUND);
|
return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.COOKIE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
@ -449,4 +451,8 @@ public class SessionCodeChecks {
|
||||||
protected EventBuilder getEvent() {
|
protected EventBuilder getEvent() {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean checkClientDisabled(ClientModel client) {
|
||||||
|
return !client.isEnabled();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,4 +142,9 @@ public class ClientAttributeUpdater extends ServerResourceUpdater<ClientAttribut
|
||||||
rep.setDirectAccessGrantsEnabled(directAccessGranted);
|
rep.setDirectAccessGrantsEnabled(directAccessGranted);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ClientAttributeUpdater setEnabled(Boolean enabled){
|
||||||
|
rep.setEnabled(enabled);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,12 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.oauth;
|
package org.keycloak.testsuite.oauth;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import javax.ws.rs.NotFoundException;
|
import javax.ws.rs.NotFoundException;
|
||||||
|
|
||||||
|
import org.hamcrest.MatcherAssert;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -30,6 +32,7 @@ import org.junit.Test;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
|
import org.keycloak.admin.client.resource.ClientsResource;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
|
@ -48,6 +51,7 @@ import org.keycloak.testsuite.pages.InfoPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.LogoutConfirmPage;
|
import org.keycloak.testsuite.pages.LogoutConfirmPage;
|
||||||
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
||||||
|
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
||||||
import org.keycloak.testsuite.util.InfinispanTestTimeServiceRule;
|
import org.keycloak.testsuite.util.InfinispanTestTimeServiceRule;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.ServerURLs;
|
import org.keycloak.testsuite.util.ServerURLs;
|
||||||
|
@ -206,7 +210,35 @@ public class LegacyLogoutTest extends AbstractTestRealmKeycloakTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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).removeDetail(Details.REDIRECT_URI).assertEvent();
|
||||||
|
MatcherAssert.assertThat(false, is(isSessionActive(sessionId)));
|
||||||
|
assertCurrentUrlEquals(APP_REDIRECT_URI);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private OAuthClient.AccessTokenResponse loginUser() {
|
private OAuthClient.AccessTokenResponse loginUser() {
|
||||||
oauth.doLogin("test-user@localhost", "password");
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
|
|
@ -93,7 +93,7 @@ import org.openqa.selenium.NoSuchElementException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for OIDC RP-Initiated Logout - https://openid.net/specs/openid-connect-rpinitiated-1_0.html
|
* Test for OIDC RP-Initiated Logout - https://openid.net/specs/openid-connect-rpinitiated-1_0.html
|
||||||
*
|
* <p>
|
||||||
* This is handled on server-side by the LogoutEndpoint.logout method
|
* This is handled on server-side by the LogoutEndpoint.logout method
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -141,6 +141,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void logoutRedirect() {
|
public void logoutRedirect() {
|
||||||
|
|
||||||
OAuthClient.AccessTokenResponse tokenResponse = loginUser();
|
OAuthClient.AccessTokenResponse tokenResponse = loginUser();
|
||||||
String sessionId = tokenResponse.getSessionState();
|
String sessionId = tokenResponse.getSessionState();
|
||||||
|
|
||||||
|
@ -506,8 +507,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
|
||||||
try {
|
try {
|
||||||
logoutConfirmPage.clickBackToApplicationLink();
|
logoutConfirmPage.clickBackToApplicationLink();
|
||||||
fail();
|
fail();
|
||||||
}
|
} catch (NoSuchElementException ex) {
|
||||||
catch (NoSuchElementException ex) {
|
|
||||||
// expected
|
// expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -562,8 +562,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
|
||||||
try {
|
try {
|
||||||
errorPage.clickBackToApplication();
|
errorPage.clickBackToApplication();
|
||||||
fail();
|
fail();
|
||||||
}
|
} catch (NoSuchElementException ex) {
|
||||||
catch (NoSuchElementException ex) {
|
|
||||||
// expected
|
// expected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -760,8 +759,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
|
||||||
try {
|
try {
|
||||||
logoutConfirmPage.clickBackToApplicationLink();
|
logoutConfirmPage.clickBackToApplicationLink();
|
||||||
fail();
|
fail();
|
||||||
}
|
} catch (NoSuchElementException ex) {
|
||||||
catch (NoSuchElementException ex) {
|
|
||||||
// expected
|
// expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -880,8 +878,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
|
||||||
try {
|
try {
|
||||||
logoutConfirmPage.clickBackToApplicationLink();
|
logoutConfirmPage.clickBackToApplicationLink();
|
||||||
fail();
|
fail();
|
||||||
}
|
} catch (NoSuchElementException ex) {
|
||||||
catch (NoSuchElementException ex) {
|
|
||||||
// expected
|
// expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1029,7 +1026,49 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void logoutWithIdTokenAndDisabledClientMustWork() throws Exception {
|
||||||
|
OAuthClient.AccessTokenResponse tokenResponse = loginUser();
|
||||||
|
|
||||||
|
|
||||||
|
try (Closeable accountClientUpdater = ClientAttributeUpdater.forClient(adminClient, "test", oauth.getClientId())
|
||||||
|
.setEnabled(false).update()) {
|
||||||
|
|
||||||
|
String logoutUrl = oauth.getLogoutUrl().postLogoutRedirectUri(APP_REDIRECT_URI).clientId("test-app").build();
|
||||||
|
driver.navigate().to(logoutUrl);
|
||||||
|
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
|
||||||
|
events.assertEmpty();
|
||||||
|
|
||||||
|
logoutConfirmPage.confirmLogout();
|
||||||
|
events.expectLogout(tokenResponse.getSessionState()).assertEvent();
|
||||||
|
MatcherAssert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Login and logout with account client disabled after login
|
||||||
|
@Test
|
||||||
|
public void testLogoutWhenAccountClientIsDisabled() throws IOException {
|
||||||
|
|
||||||
|
OAuthClient.AccessTokenResponse tokenResponse = loginUser();
|
||||||
|
String sessionId = tokenResponse.getSessionState();
|
||||||
|
|
||||||
|
try (Closeable accountClientUpdater = ClientAttributeUpdater.forClient(adminClient, "test", Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)
|
||||||
|
.setEnabled(false)
|
||||||
|
.update()) {
|
||||||
|
String logoutUrl = oauth.getLogoutUrl().build();
|
||||||
|
driver.navigate().to(logoutUrl);
|
||||||
|
|
||||||
|
events.assertEmpty();
|
||||||
|
logoutConfirmPage.assertCurrent();
|
||||||
|
logoutConfirmPage.confirmLogout();
|
||||||
|
|
||||||
|
MatcherAssert.assertThat(false, is(isSessionActive(sessionId)));
|
||||||
|
MatcherAssert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SUPPORT METHODS
|
||||||
private OAuthClient.AccessTokenResponse loginUser() {
|
private OAuthClient.AccessTokenResponse loginUser() {
|
||||||
return loginUser(false);
|
return loginUser(false);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue