KEYCLOAK-1216 Click on 'Logout all sessions' in Account mgmt should propagate logout to the apps

This commit is contained in:
mposolda 2015-04-24 18:42:03 +02:00
parent 767d1fcc20
commit a338626d2b
12 changed files with 110 additions and 15 deletions

View file

@ -165,7 +165,8 @@ public class OIDCLoginProtocol implements LoginProtocol {
@Override @Override
public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) { public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
if (!(clientSession.getClient() instanceof ClientModel)) return; if (!(clientSession.getClient() instanceof ClientModel)) return;
ClientModel app = (ClientModel)clientSession.getClient(); ClientModel app = clientSession.getClient();
// TODO: Probably non-effective to build executor every time from scratch. Should be likely shared for whole OIDCLoginProtocolFactory
ApacheHttpClient4Executor executor = ResourceAdminManager.createExecutor(); ApacheHttpClient4Executor executor = ResourceAdminManager.createExecutor();
try { try {

View file

@ -113,22 +113,26 @@ public class AuthenticationManager {
expireUserSessionCookie(session, userSession, realm, uriInfo, headers, connection); expireUserSessionCookie(session, userSession, realm, uriInfo, headers, connection);
for (ClientSessionModel clientSession : userSession.getClientSessions()) { for (ClientSessionModel clientSession : userSession.getClientSessions()) {
ClientModel client = clientSession.getClient(); backchannelLogoutClientSession(session, realm, clientSession, userSession, uriInfo, headers);
if (client instanceof ClientModel && !client.isFrontchannelLogout() && clientSession.getAction() != ClientSessionModel.Action.LOGGED_OUT) {
String authMethod = clientSession.getAuthMethod();
if (authMethod == null) continue; // must be a keycloak service like account
LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
protocol.setRealm(realm)
.setHttpHeaders(headers)
.setUriInfo(uriInfo);
protocol.backchannelLogout(userSession, clientSession);
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
}
} }
userSession.setState(UserSessionModel.State.LOGGED_OUT); userSession.setState(UserSessionModel.State.LOGGED_OUT);
session.sessions().removeUserSession(realm, userSession); session.sessions().removeUserSession(realm, userSession);
} }
public static void backchannelLogoutClientSession(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession, UserSessionModel userSession, UriInfo uriInfo, HttpHeaders headers) {
ClientModel client = clientSession.getClient();
if (client instanceof ClientModel && !client.isFrontchannelLogout() && clientSession.getAction() != ClientSessionModel.Action.LOGGED_OUT) {
String authMethod = clientSession.getAuthMethod();
if (authMethod == null) return; // must be a keycloak service like account
LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
protocol.setRealm(realm)
.setHttpHeaders(headers)
.setUriInfo(uriInfo);
protocol.backchannelLogout(userSession, clientSession);
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
}
}
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) {
if (userSession == null) return null; if (userSession == null) return null;

View file

@ -478,7 +478,10 @@ public class AccountService {
csrfCheck(stateChecker); csrfCheck(stateChecker);
UserModel user = auth.getUser(); UserModel user = auth.getUser();
session.sessions().removeUserSessions(realm, user); List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
for (UserSessionModel userSession : userSessions) {
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
}
UriBuilder builder = Urls.accountBase(uriInfo.getBaseUri()).path(AccountService.class, "sessionsPage"); UriBuilder builder = Urls.accountBase(uriInfo.getBaseUri()).path(AccountService.class, "sessionsPage");
String referrer = uriInfo.getQueryParameters().getFirst("referrer"); String referrer = uriInfo.getQueryParameters().getFirst("referrer");
@ -519,6 +522,7 @@ public class AccountService {
List<ClientSessionModel> clientSessions = userSession.getClientSessions(); List<ClientSessionModel> clientSessions = userSession.getClientSessions();
for (ClientSessionModel clientSession : clientSessions) { for (ClientSessionModel clientSession : clientSessions) {
if (clientSession.getClient().getId().equals(clientId)) { if (clientSession.getClient().getId().equals(clientId)) {
AuthenticationManager.backchannelLogoutClientSession(session, realm, clientSession, userSession, uriInfo, headers);
TokenManager.dettachClientSession(session.sessions(), realm, clientSession); TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
} }
} }

View file

@ -155,4 +155,12 @@ public class AdapterTest {
testStrategy.testAdminApplicationLogout(); testStrategy.testAdminApplicationLogout();
} }
/**
* KEYCLOAK-1216
*/
@Test
public void testAccountManagementSessionsLogout() throws Throwable {
testStrategy.testAccountManagementSessionsLogout();
}
} }

View file

@ -44,6 +44,7 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.resources.admin.AdminRoot; import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AccountSessionsPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule; import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.KeycloakRule;
@ -94,6 +95,9 @@ public class AdapterTestStrategy extends ExternalResource {
@WebResource @WebResource
protected InputPage inputPage; protected InputPage inputPage;
@WebResource
protected AccountSessionsPage accountSessionsPage;
protected String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(AUTH_SERVER_URL)).build("demo").toString(); protected String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(AUTH_SERVER_URL)).build("demo").toString();
public AdapterTestStrategy(String AUTH_SERVER_URL, String APP_SERVER_BASE_URL, AbstractKeycloakRule keycloakRule) { public AdapterTestStrategy(String AUTH_SERVER_URL, String APP_SERVER_BASE_URL, AbstractKeycloakRule keycloakRule) {
@ -592,6 +596,22 @@ public class AdapterTestStrategy extends ExternalResource {
Assert.assertTrue(pageSource.contains("Counter=3")); Assert.assertTrue(pageSource.contains("Counter=3"));
} }
/**
* KEYCLOAK-1216
*/
public void testAccountManagementSessionsLogout() throws Throwable {
// login as bburke
loginAndCheckSession(driver, loginPage);
// logout sessions in account management
accountSessionsPage.realm("demo");
accountSessionsPage.open();
accountSessionsPage.logoutAll();
// Assert I need to login again (logout was propagated to the app)
loginAndCheckSession(driver, loginPage);
}
protected void loginAndCheckSession(WebDriver driver, LoginPage loginPage) { protected void loginAndCheckSession(WebDriver driver, LoginPage loginPage) {
driver.navigate().to(APP_SERVER_BASE_URL + "/session-portal"); driver.navigate().to(APP_SERVER_BASE_URL + "/session-portal");
String currentUrl = driver.getCurrentUrl(); String currentUrl = driver.getCurrentUrl();

View file

@ -36,7 +36,9 @@ import java.util.List;
*/ */
public class AccountSessionsPage extends AbstractAccountPage { public class AccountSessionsPage extends AbstractAccountPage {
private static String PATH = Urls.accountSessionsPage(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT).build(), "test").toString(); private String realmName = "test";
private String path = Urls.accountSessionsPage(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT).build(), "test").toString();
@FindBy(id = "logout-all-sessions") @FindBy(id = "logout-all-sessions")
private WebElement logoutAllLink; private WebElement logoutAllLink;
@ -45,8 +47,16 @@ public class AccountSessionsPage extends AbstractAccountPage {
return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().endsWith("/account/sessions"); return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().endsWith("/account/sessions");
} }
public void realm(String realmName) {
this.realmName = realmName;
}
public String getPath() {
return Urls.accountSessionsPage(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT).build(), realmName).toString();
}
public void open() { public void open() {
driver.navigate().to(PATH); driver.navigate().to(getPath());
} }
public void logoutAll() { public void logoutAll() {

View file

@ -172,4 +172,12 @@ public class Jetty8Test {
public void testAdminApplicationLogout() throws Throwable { public void testAdminApplicationLogout() throws Throwable {
testStrategy.testAdminApplicationLogout(); testStrategy.testAdminApplicationLogout();
} }
/**
* KEYCLOAK-1216
*/
@Test
public void testAccountManagementSessionsLogout() throws Throwable {
testStrategy.testAccountManagementSessionsLogout();
}
} }

View file

@ -172,4 +172,12 @@ public class Jetty9Test {
public void testAdminApplicationLogout() throws Throwable { public void testAdminApplicationLogout() throws Throwable {
testStrategy.testAdminApplicationLogout(); testStrategy.testAdminApplicationLogout();
} }
/**
* KEYCLOAK-1216
*/
@Test
public void testAccountManagementSessionsLogout() throws Throwable {
testStrategy.testAccountManagementSessionsLogout();
}
} }

View file

@ -172,4 +172,12 @@ public class Jetty9Test {
public void testAdminApplicationLogout() throws Throwable { public void testAdminApplicationLogout() throws Throwable {
testStrategy.testAdminApplicationLogout(); testStrategy.testAdminApplicationLogout();
} }
/**
* KEYCLOAK-1216
*/
@Test
public void testAccountManagementSessionsLogout() throws Throwable {
testStrategy.testAccountManagementSessionsLogout();
}
} }

View file

@ -161,6 +161,14 @@ public class TomcatTest {
testStrategy.testAdminApplicationLogout(); testStrategy.testAdminApplicationLogout();
} }
/**
* KEYCLOAK-1216
*/
@Test
public void testAccountManagementSessionsLogout() throws Throwable {
testStrategy.testAccountManagementSessionsLogout();
}
static String getBaseDirectory() { static String getBaseDirectory() {
String dirPath = null; String dirPath = null;
String relativeDirPath = "testsuite" + File.separator + "tomcat6" + File.separator + "target"; String relativeDirPath = "testsuite" + File.separator + "tomcat6" + File.separator + "target";

View file

@ -165,6 +165,14 @@ public class Tomcat7Test {
testStrategy.testAdminApplicationLogout(); testStrategy.testAdminApplicationLogout();
} }
/**
* KEYCLOAK-1216
*/
@Test
public void testAccountManagementSessionsLogout() throws Throwable {
testStrategy.testAccountManagementSessionsLogout();
}
private static String getBaseDirectory() { private static String getBaseDirectory() {
String dirPath = null; String dirPath = null;

View file

@ -166,6 +166,14 @@ public class TomcatTest {
testStrategy.testAdminApplicationLogout(); testStrategy.testAdminApplicationLogout();
} }
/**
* KEYCLOAK-1216
*/
@Test
public void testAccountManagementSessionsLogout() throws Throwable {
testStrategy.testAccountManagementSessionsLogout();
}
private static String getBaseDirectory() { private static String getBaseDirectory() {
String dirPath = null; String dirPath = null;
String relativeDirPath = "testsuite" + File.separator + "tomcat8" + File.separator + "target"; String relativeDirPath = "testsuite" + File.separator + "tomcat8" + File.separator + "target";