keycloak-aplcache

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());
+    }
+
 }