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 7d68c18..85ace35 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<String> 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 921e3db..c0ca7f1 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<String, AccountApplicationsPage.AppEntry> 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<Map<String, Object>> consents = user.getConsents();
+ Assert.assertTrue(consents.isEmpty());
+ }
}