keycloak-aplcache

Details

diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
index 2c6addc..645a76f 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
@@ -53,17 +53,24 @@ public class ClientSessionAdapter implements ClientSessionModel {
 
     @Override
     public void setUserSession(UserSessionModel userSession) {
-        if (entity.getUserSession() != null) {
-            if (entity.getUserSession().equals(userSession.getId())) {
-                return;
-            } else {
-                provider.dettachSession(userSession, this);
+        if (userSession == null) {
+            if (entity.getUserSession() != null) {
+                provider.dettachSession(getUserSession(), this);
             }
+            entity.setUserSession(null);
         } else {
-            provider.attachSession(userSession, this);
-        }
+            if (entity.getUserSession() != null) {
+                if (entity.getUserSession().equals(userSession.getId())) {
+                    return;
+                } else {
+                    provider.dettachSession(userSession, this);
+                }
+            } else {
+                provider.attachSession(userSession, this);
+            }
 
-        entity.setUserSession(userSession.getId());
+            entity.setUserSession(userSession.getId());
+        }
         update();
     }
 
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java
index e05130c..18b0332 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java
@@ -87,10 +87,17 @@ public class ClientSessionAdapter implements ClientSessionModel {
 
     @Override
     public void setUserSession(UserSessionModel userSession) {
-        UserSessionAdapter adapter = (UserSessionAdapter)userSession;
-        UserSessionEntity userSessionEntity = adapter.getEntity();
-        entity.setSession(userSessionEntity);
-        userSessionEntity.getClientSessions().add(entity);
+        if (userSession == null) {
+            if (entity.getSession() != null) {
+                entity.getSession().getClientSessions().remove(entity);
+            }
+            entity.setSession(null);
+        } else {
+            UserSessionAdapter adapter = (UserSessionAdapter) userSession;
+            UserSessionEntity userSessionEntity = adapter.getEntity();
+            entity.setSession(userSessionEntity);
+            userSessionEntity.getClientSessions().add(entity);
+        }
     }
 
     @Override
@@ -109,6 +116,13 @@ public class ClientSessionAdapter implements ClientSessionModel {
 
                 entity.getRoles().add(roleEntity);
             }
+        } else {
+            if (entity.getRoles() != null) {
+                for (ClientSessionRoleEntity r : entity.getRoles()) {
+                    em.remove(r);
+                }
+                entity.getRoles().clear();
+            }
         }
     }
 
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java
index e807861..97aa742 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java
@@ -54,7 +54,7 @@ public class UserSessionEntity {
     @Column(name="LAST_SESSION_REFRESH")
     protected int lastSessionRefresh;
 
-    @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="session")
+    @OneToMany(mappedBy="session")
     protected Collection<ClientSessionEntity> clientSessions = new ArrayList<ClientSessionEntity>();
 
     public String getId() {
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
index 6708a2d..760e368 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
@@ -155,6 +155,9 @@ public class JpaUserSessionProvider implements UserSessionProvider {
     public void removeUserSession(RealmModel realm, UserSessionModel session) {
         UserSessionEntity entity = em.find(UserSessionEntity.class, session.getId());
         if (entity != null) {
+            for (ClientSessionEntity c : entity.getClientSessions()) {
+                em.remove(c);
+            }
             em.remove(entity);
         }
     }
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java
index 1328171..9981fb7 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java
@@ -52,10 +52,17 @@ public class ClientSessionAdapter implements ClientSessionModel {
 
     @Override
     public void setUserSession(UserSessionModel userSession) {
-        UserSessionAdapter adapter = (UserSessionAdapter)userSession;
-        UserSessionEntity userSessionEntity = adapter.getEntity();
-        entity.setSession(userSessionEntity);
-        userSessionEntity.getClientSessions().add(entity);
+        if (userSession == null) {
+            if (entity.getSession() != null) {
+                entity.getSession().getClientSessions().remove(entity);
+            }
+            entity.setSession(null);
+        } else {
+            UserSessionAdapter adapter = (UserSessionAdapter) userSession;
+            UserSessionEntity userSessionEntity = adapter.getEntity();
+            entity.setSession(userSessionEntity);
+            userSessionEntity.getClientSessions().add(entity);
+        }
     }
 
     @Override
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java
index 92773f8..2ab8630 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java
@@ -55,11 +55,19 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
 
     @Override
     public void setUserSession(UserSessionModel userSession) {
-        MongoUserSessionEntity userSessionEntity = provider.getUserSessionEntity(realm, userSession.getId());
-        entity.setSessionId(userSessionEntity.getId());
-        updateMongoEntity();
+        if (userSession == null) {
+            if (entity.getSessionId() != null) {
+                MongoUserSessionEntity userSessionEntity = provider.getUserSessionEntity(realm, entity.getSessionId());
+                provider.getMongoStore().pullItemFromList(userSessionEntity, "clientSessions", entity.getSessionId(), invocationContext);
+            }
+            entity.setSessionId(null);
+        } else {
+            MongoUserSessionEntity userSessionEntity = provider.getUserSessionEntity(realm, userSession.getId());
+            entity.setSessionId(userSessionEntity.getId());
+            updateMongoEntity();
 
-        provider.getMongoStore().pushItemToList(userSessionEntity, "clientSessions", entity.getId(), true, invocationContext);
+            provider.getMongoStore().pushItemToList(userSessionEntity, "clientSessions", entity.getId(), true, invocationContext);
+        }
     }
 
     @Override
@@ -70,9 +78,13 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
 
     @Override
     public void setRoles(Set<String> roles) {
-        List<String> list = new LinkedList<String>();
-        list.addAll(roles);
-        entity.setRoles(list);
+        if (roles == null) {
+            entity.setRoles(null);
+        } else {
+            List<String> list = new LinkedList<String>();
+            list.addAll(roles);
+            entity.setRoles(list);
+        }
         updateMongoEntity();
     }
 
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 7dad225..f6911dc 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -17,6 +17,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
@@ -136,11 +137,22 @@ public class TokenManager {
             requestedRoles.add(r.getId());
         }
         clientSession.setRoles(requestedRoles);
+    }
+
+    public static void dettachClientSession(UserSessionProvider sessions, RealmModel realm, ClientSessionModel clientSession) {
+        UserSessionModel userSession = clientSession.getUserSession();
+        if (userSession == null) {
+            return;
+        }
 
+        clientSession.setUserSession(null);
+        clientSession.setRoles(null);
 
+        if (userSession.getClientSessions().isEmpty()) {
+            sessions.removeUserSession(realm, userSession);
+        }
     }
 
-
     public static Set<RoleModel> getAccess(String scopeParam, ClientModel client, UserModel user) {
         // todo scopeParam is ignored until we figure out a scheme that fits with openid connect
         Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 84f7cdb..33988da 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -200,7 +200,10 @@ public class LoginActionsService {
         ClientSessionCode clientSessionCode = checks.clientCode;
         ClientSessionModel clientSession = clientSessionCode.getClientSession();
 
-
+        if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
+            TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
+            clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
+        }
 
         LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
                 .setClientSessionCode(clientSessionCode.getCode());
@@ -267,7 +270,7 @@ public class LoginActionsService {
             return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
         }
         ClientSessionModel clientSession = clientCode.getClientSession();
-        if (!(clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientCode.isValid(ClientSessionModel.Action.RECOVER_PASSWORD))) {
+        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) {
             clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
             event.client(clientSession.getClient()).error(Errors.INVALID_CODE);
             return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo).setError(Messages.INVALID_USER)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 07b24d7..6ac7860 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -27,6 +27,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
+import org.keycloak.events.Event;
 import org.keycloak.events.EventType;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
@@ -152,6 +153,36 @@ public class ResetPasswordTest {
     }
 
     @Test
+    public void resetPasswordCancelChangeUser() throws IOException, MessagingException {
+        loginPage.open();
+        loginPage.resetPassword();
+
+        resetPasswordPage.assertCurrent();
+
+        resetPasswordPage.changePassword("test-user@localhost");
+
+        resetPasswordPage.assertCurrent();
+
+        events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).detail(Details.USERNAME, "test-user@localhost").detail(Details.EMAIL, "test-user@localhost").assertEvent().getSessionId();
+
+        resetPasswordPage.backToLogin();
+
+        Assert.assertTrue(loginPage.isCurrent());
+
+        loginPage.login("login@test.com", "password");
+
+        Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent();
+
+        String code = oauth.getCurrentQuery().get("code");
+        OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+
+        Assert.assertEquals(200, tokenResponse.getStatusCode());
+        Assert.assertEquals(userId, oauth.verifyToken(tokenResponse.getAccessToken()).getSubject());
+
+        events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), loginEvent.getSessionId()).user(userId).assertEvent();
+    }
+
+    @Test
     public void resetPasswordByEmail() throws IOException, MessagingException {
         resetPassword("login@test.com");
     }