commit
05d0f1d2ee
14 changed files with 113 additions and 18 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -379,7 +379,7 @@
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<keycloak.realm.provider>jpa</keycloak.realm.provider>
|
<keycloak.realm.provider>jpa</keycloak.realm.provider>
|
||||||
<keycloak.user.provider>jpa</keycloak.user.provider>
|
<keycloak.user.provider>jpa</keycloak.user.provider>
|
||||||
<keycloak.eventStore.provider>jpa</keycloak.eventStore.provider>
|
<keycloak.eventsStore.provider>jpa</keycloak.eventsStore.provider>
|
||||||
<keycloak.userSessions.provider>jpa</keycloak.userSessions.provider>
|
<keycloak.userSessions.provider>jpa</keycloak.userSessions.provider>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -443,7 +443,7 @@
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<keycloak.realm.provider>mongo</keycloak.realm.provider>
|
<keycloak.realm.provider>mongo</keycloak.realm.provider>
|
||||||
<keycloak.user.provider>mongo</keycloak.user.provider>
|
<keycloak.user.provider>mongo</keycloak.user.provider>
|
||||||
<keycloak.audit.provider>mongo</keycloak.audit.provider>
|
<keycloak.eventsStore.provider>mongo</keycloak.eventsStore.provider>
|
||||||
<keycloak.userSessions.provider>mongo</keycloak.userSessions.provider>
|
<keycloak.userSessions.provider>mongo</keycloak.userSessions.provider>
|
||||||
<keycloak.connectionsMongo.host>${keycloak.connectionsMongo.host}</keycloak.connectionsMongo.host>
|
<keycloak.connectionsMongo.host>${keycloak.connectionsMongo.host}</keycloak.connectionsMongo.host>
|
||||||
<keycloak.connectionsMongo.port>${keycloak.connectionsMongo.port}</keycloak.connectionsMongo.port>
|
<keycloak.connectionsMongo.port>${keycloak.connectionsMongo.port}</keycloak.connectionsMongo.port>
|
||||||
|
|
|
@ -155,4 +155,12 @@ public class AdapterTest {
|
||||||
testStrategy.testAdminApplicationLogout();
|
testStrategy.testAdminApplicationLogout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KEYCLOAK-1216
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAccountManagementSessionsLogout() throws Throwable {
|
||||||
|
testStrategy.testAccountManagementSessionsLogout();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"eventsStore": {
|
"eventsStore": {
|
||||||
"provider": "${keycloak.eventStore.provider:jpa}"
|
"provider": "${keycloak.eventsStore.provider:jpa}"
|
||||||
},
|
},
|
||||||
|
|
||||||
"eventsListener": {
|
"eventsListener": {
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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";
|
||||||
|
|
Loading…
Reference in a new issue