From 1fd5af840be3f089856e4dcaa11e2eaf5f258644 Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 24 Apr 2017 11:24:09 +0200 Subject: [PATCH] KEYCLOAK-4525 Deleting a client with existing sessions/offline_tokens leads to Internal Server Errors --- .../InfinispanUserSessionProvider.java | 5 ++ .../testsuite/oauth/OfflineTokenTest.java | 68 +++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java index 7d68c18ae4..85ace35908 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java @@ -637,6 +637,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { while(itr.hasNext()) { UserSessionEntity entity = (UserSessionEntity) itr.next().getValue(); Set currClientSessions = entity.getClientSessions(); + + if (currClientSessions == null) { + continue; + } + for (String clientSessionId : currClientSessions) { ClientSessionEntity cls = (ClientSessionEntity) offlineSessionCache.get(clientSessionId); if (cls != null) { 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 921e3dbd24..c0ca7f119b 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 @@ -43,9 +43,11 @@ import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.account.AccountTest; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; import org.keycloak.testsuite.auth.page.AuthRealm; +import org.keycloak.testsuite.pages.AccountApplicationsPage; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientManager; @@ -58,6 +60,7 @@ import org.keycloak.util.TokenUtil; import java.util.Collections; import java.util.List; +import java.util.Map; import javax.ws.rs.NotFoundException; @@ -79,6 +82,10 @@ public class OfflineTokenTest extends AbstractKeycloakTest { @Page protected LoginPage loginPage; + + @Page + protected AccountApplicationsPage applicationsPage; + @Rule public AssertEvents events = new AssertEvents(this); @@ -482,4 +489,65 @@ public class OfflineTokenTest extends AbstractKeycloakTest { RealmRepresentation testRealm = offlineTokenAdmin.realm("test").toRepresentation(); Assert.assertNotNull(testRealm); } + + + // KEYCLOAK-4525 + @Test + public void offlineTokenRemoveClientWithTokens() throws Exception { + // Create new client + RealmResource appRealm = adminClient.realm("test"); + + ClientRepresentation clientRep = ClientBuilder.create().clientId("offline-client-2") + .id(KeycloakModelUtils.generateId()) + .directAccessGrants() + .secret("secret1").build(); + + appRealm.clients().create(clientRep); + + // Direct grant login requesting offline token + oauth.scope(OAuth2Constants.OFFLINE_ACCESS); + oauth.clientId("offline-client-2"); + OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("secret1", "test-user@localhost", "password"); + Assert.assertNull(tokenResponse.getErrorDescription()); + AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken()); + String offlineTokenString = tokenResponse.getRefreshToken(); + RefreshToken offlineToken = oauth.verifyRefreshToken(offlineTokenString); + + events.expectLogin() + .client("offline-client-2") + .user(userId) + .session(token.getSessionState()) + .detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD) + .detail(Details.TOKEN_ID, token.getId()) + .detail(Details.REFRESH_TOKEN_ID, offlineToken.getId()) + .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE) + .detail(Details.USERNAME, "test-user@localhost") + .removeDetail(Details.CODE_ID) + .removeDetail(Details.REDIRECT_URI) + .removeDetail(Details.CONSENT) + .assertEvent(); + + // Go to account mgmt applications page + applicationsPage.open(); + loginPage.login("test-user@localhost", "password"); + events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountTest.ACCOUNT_REDIRECT + "?path=applications").assertEvent(); + Assert.assertTrue(applicationsPage.isCurrent()); + Map apps = applicationsPage.getApplications(); + Assert.assertTrue(apps.containsKey("offline-client-2")); + Assert.assertEquals("Offline Token", apps.get("offline-client-2").getAdditionalGrants().get(0)); + + // Now remove the client + ClientResource offlineTokenClient2 = ApiUtil.findClientByClientId(appRealm, "offline-client-2" ); + offlineTokenClient2.remove(); + + // Go to applications page and see offline-client not anymore + applicationsPage.open(); + apps = applicationsPage.getApplications(); + Assert.assertFalse(apps.containsKey("offline-client-2")); + + // Login as admin and see consents of user + UserResource user = ApiUtil.findUserByUsernameId(appRealm, "test-user@localhost"); + List> consents = user.getConsents(); + Assert.assertTrue(consents.isEmpty()); + } }