Details
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 4c58f3a..9399c68 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
@@ -40,8 +40,10 @@ import org.keycloak.representations.RefreshToken;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.Cors;
+import org.keycloak.util.TokenUtil;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@@ -181,9 +183,19 @@ public class LogoutEndpoint {
}
try {
RefreshToken token = tokenManager.verifyRefreshToken(session, realm, refreshToken, false);
- UserSessionModel userSessionModel = session.sessions().getUserSession(realm, token.getSessionState());
+
+ boolean offline = TokenUtil.TOKEN_TYPE_OFFLINE.equals(token.getType());
+
+ UserSessionModel userSessionModel;
+ if (offline) {
+ UserSessionManager sessionManager = new UserSessionManager(session);
+ userSessionModel = sessionManager.findOfflineUserSession(realm, token.getSessionState());
+ } else {
+ userSessionModel = session.sessions().getUserSession(realm, token.getSessionState());
+ }
+
if (userSessionModel != null) {
- logout(userSessionModel);
+ logout(userSessionModel, offline);
}
} catch (OAuthErrorException e) {
event.error(Errors.INVALID_TOKEN);
@@ -192,8 +204,8 @@ public class LogoutEndpoint {
return Cors.add(request, Response.noContent()).auth().allowedOrigins(uriInfo, client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
}
- private void logout(UserSessionModel userSession) {
- AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
+ private void logout(UserSessionModel userSession, boolean offline) {
+ AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true, offline);
event.user(userSession.getUser()).session(userSession).success();
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 38c43da..32f8645 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -155,6 +155,12 @@ public class AuthenticationManager {
);
}
+ public static void backchannelLogout(KeycloakSession session, RealmModel realm,
+ UserSessionModel userSession, UriInfo uriInfo,
+ ClientConnection connection, HttpHeaders headers,
+ boolean logoutBroker) {
+ backchannelLogout(session, realm, userSession, uriInfo, connection, headers, logoutBroker, false);
+ }
/**
* Do not logout broker
@@ -169,7 +175,8 @@ public class AuthenticationManager {
public static void backchannelLogout(KeycloakSession session, RealmModel realm,
UserSessionModel userSession, UriInfo uriInfo,
ClientConnection connection, HttpHeaders headers,
- boolean logoutBroker) {
+ boolean logoutBroker,
+ boolean offlineSession) {
if (userSession == null) return;
UserModel user = userSession.getUser();
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
@@ -190,7 +197,12 @@ public class AuthenticationManager {
}
userSession.setState(UserSessionModel.State.LOGGED_OUT);
- session.sessions().removeUserSession(realm, userSession);
+
+ if (offlineSession) {
+ session.sessions().removeOfflineUserSession(realm, userSession);
+ } else {
+ session.sessions().removeUserSession(realm, userSession);
+ }
}
private static AuthenticationSessionModel createOrJoinLogoutSession(RealmModel realm, final AuthenticationSessionManager asm, boolean browserCookie) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
index 6254b51..2c7af8d 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
@@ -17,6 +17,7 @@
package org.keycloak.testsuite.oauth;
+import org.apache.http.client.methods.CloseableHttpResponse;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Before;
@@ -58,12 +59,11 @@ import org.keycloak.testsuite.util.RoleBuilder;
import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.util.TokenUtil;
+import javax.ws.rs.NotFoundException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import javax.ws.rs.NotFoundException;
-
import static org.junit.Assert.assertEquals;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.admin.ApiUtil.findRealmRoleByName;
@@ -550,4 +550,22 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
List<Map<String, Object>> consents = user.getConsents();
Assert.assertTrue(consents.isEmpty());
}
+
+ @Test
+ public void offlineTokenLogout() throws Exception {
+ oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
+ oauth.clientId("offline-client");
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret1", "test-user@localhost", "password");
+ assertEquals(200, response.getStatusCode());
+
+ response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret1");
+ assertEquals(200, response.getStatusCode());
+
+ CloseableHttpResponse logoutResponse = oauth.doLogout(response.getRefreshToken(), "secret1");
+ assertEquals(204, logoutResponse.getStatusLine().getStatusCode());
+
+ response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret1");
+ assertEquals(400, response.getStatusCode());
+ }
+
}