keycloak-aplcache

Merge pull request #697 from mposolda/master Cookie & rememberMe

9/16/2014 5:15:27 PM

Details

diff --git a/forms/common-themes/src/main/resources/theme/login/base/login.ftl b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
index 6fd5808..8ec2470 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
@@ -40,7 +40,11 @@
                         <#if realm.rememberMe>
                             <div class="checkbox">
                                 <label>
-                                    <input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> Remember Me
+                                    <#if login.rememberMe??>
+                                        <input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3" checked> Remember Me
+                                    <#else>
+                                        <input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> Remember Me
+                                    </#if>
                                 </label>
                             </div>
                         </#if>
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/LoginBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/LoginBean.java
index 22dda67..c5e3048 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/LoginBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/LoginBean.java
@@ -34,11 +34,14 @@ public class LoginBean {
 
     private String passwordToken;
 
+    private String rememberMe;
+
     public LoginBean(MultivaluedMap<String, String> formData){
         if (formData != null) {
             username = formData.getFirst("username");
             password = formData.getFirst("password");
             passwordToken = formData.getFirst("password-token");
+            rememberMe = formData.getFirst("rememberMe");
         }
     }
 
@@ -53,4 +56,8 @@ public class LoginBean {
     public String getPasswordToken() {
         return passwordToken;
     }
+
+    public String getRememberMe() {
+        return rememberMe;
+    }
 }
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
index e4e75c5..3fdfdba 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
@@ -70,6 +70,9 @@ public class MongoUserSessionProvider implements UserSessionProvider {
 
         MongoUserSessionEntity userSessionEntity = entities.get(0);
         List<MongoClientSessionEntity> sessions = userSessionEntity.getClientSessions();
+        if (sessions == null) {
+            return null;
+        }
         for (MongoClientSessionEntity s : sessions) {
             if (s.getId().equals(id)) {
                 return new ClientSessionAdapter(session, this, realm, s, userSessionEntity, invocationContext);
@@ -240,6 +243,10 @@ public class MongoUserSessionProvider implements UserSessionProvider {
                 .get();
         List<MongoUserSessionEntity> userSessionEntities = mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext);
         for (MongoUserSessionEntity e : userSessionEntities) {
+            if (e.getClientSessions() == null) {
+                continue;
+            }
+
             List<MongoClientSessionEntity> remove = new LinkedList<MongoClientSessionEntity>();
             for (MongoClientSessionEntity c : e.getClientSessions()) {
                 if (c.getClientId().equals(client.getId())) {
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java
index 633e1e6..6a66585 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java
@@ -131,6 +131,10 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
     @Override
     public List<ClientSessionModel> getClientSessions() {
         List<ClientSessionModel> sessions = new LinkedList<ClientSessionModel>();
+        if (entity.getClientSessions() == null) {
+            return sessions;
+        }
+
         for (MongoClientSessionEntity e : entity.getClientSessions()) {
             sessions.add(new ClientSessionAdapter(keycloakSession, provider, realm, e, entity, invocationContext));
         }
diff --git a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
index 88ebc58..e3bb569 100755
--- a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
@@ -23,7 +23,7 @@ public class AppAuthManager extends AuthenticationManager {
         if (authResult == null) return null;
         // refresh the cookies!
         createLoginCookie(realm, authResult.getUser(), authResult.getSession(), uriInfo, connection);
-        if (authResult.getSession().isRememberMe()) createRememberMeCookie(realm, uriInfo, connection);
+        if (authResult.getSession().isRememberMe()) createRememberMeCookie(realm, authResult.getUser().getUsername(), uriInfo, connection);
         return authResult;
     }
 
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 304de9d..b99df15 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -87,8 +87,8 @@ public class AuthenticationManager {
         if (session != null) {
             token.setSessionState(session.getId());
         }
-        if (realm.getSsoSessionIdleTimeout() > 0) {
-            token.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
+        if (realm.getSsoSessionMaxLifespan() > 0) {
+            token.expiration(Time.currentTime() + realm.getSsoSessionMaxLifespan());
         }
         return token;
     }
@@ -100,7 +100,7 @@ public class AuthenticationManager {
         boolean secureOnly = realm.getSslRequired().isRequired(connection);
         int maxAge = NewCookie.DEFAULT_MAX_AGE;
         if (session.isRememberMe()) {
-            maxAge = realm.getSsoSessionIdleTimeout();
+            maxAge = realm.getSsoSessionMaxLifespan();
         }
         logger.debugv("Create login cookie - name: {0}, path: {1}, max-age: {2}", KEYCLOAK_IDENTITY_COOKIE, cookiePath, maxAge);
         CookieHelper.addCookie(KEYCLOAK_IDENTITY_COOKIE, encoded, cookiePath, null, null, maxAge, secureOnly, true);
@@ -116,12 +116,12 @@ public class AuthenticationManager {
 
     }
 
-    public void createRememberMeCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) {
+    public void createRememberMeCookie(RealmModel realm, String username, UriInfo uriInfo, ClientConnection connection) {
         String path = getIdentityCookiePath(realm, uriInfo);
         boolean secureOnly = realm.getSslRequired().isRequired(connection);
-        // remember me cookie should be persistent
+        // remember me cookie should be persistent (hardcoded to 1 month for now)
         //NewCookie cookie = new NewCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getCentralLoginLifespan(), secureOnly);// todo httponly , true);
-        CookieHelper.addCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getSsoSessionIdleTimeout(), secureOnly, true);
+        CookieHelper.addCookie(KEYCLOAK_REMEMBER_ME, username, path, null, null, 2592000, secureOnly, true);
     }
 
     protected String encodeToken(RealmModel realm, Object token) {
@@ -136,7 +136,6 @@ public class AuthenticationManager {
         String path = getIdentityCookiePath(realm, uriInfo);
         expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true, connection);
         expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false, connection);
-        expireRememberMeCookie(realm, uriInfo, connection);
     }
     public static void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) {
         logger.debug("Expiring remember me cookie");
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
index d6beb17..a20f6fc 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
@@ -92,7 +92,6 @@ public class OAuthFlows {
         if (state != null)
             redirectUri.queryParam(OAuth2Constants.STATE, state);
         Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
-        Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
 
         Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
         if (sessionCookie != null) {
@@ -112,7 +111,7 @@ public class OAuthFlows {
 
         // refresh the cookies!
         authManager.createLoginCookie(realm, accessCode.getUser(), userSession, uriInfo, clientConnection);
-        if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, uriInfo, clientConnection);
+        if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, accessCode.getUser().getUsername(), uriInfo, clientConnection);
         return location.build();
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index a35ebf8..4c6c431 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -59,6 +59,7 @@ import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
@@ -523,7 +524,7 @@ public class TokenService {
         AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
 
         if (remember) {
-            authManager.createRememberMeCookie(realm, uriInfo, clientConnection);
+            authManager.createRememberMeCookie(realm, username, uriInfo, clientConnection);
         } else {
             authManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
         }
@@ -962,9 +963,21 @@ public class TokenService {
 
         LoginFormsProvider forms = Flows.forms(session, realm, client, uriInfo);
 
-        if (loginHint != null) {
+        String rememberMeUsername = null;
+        Cookie rememberMeCookie = headers.getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
+        if (rememberMeCookie != null && !"".equals(rememberMeCookie.getValue())) {
+            rememberMeUsername = rememberMeCookie.getValue();
+        }
+
+        if (loginHint != null || rememberMeUsername != null) {
             MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>();
-            formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
+
+            if (loginHint != null) {
+                formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
+            } else {
+                formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
+                formData.add("rememberMe", "on");
+            }
 
             forms.setFormData(formData);
         }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index 77f3a54..f224588 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -27,7 +27,9 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.events.Details;
+import org.keycloak.events.Event;
 import org.keycloak.models.BrowserSecurityHeaders;
+import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
@@ -166,6 +168,49 @@ public class LoginTest {
     }
 
     @Test
+    public void loginWithRememberMe() {
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setRememberMe(true);
+            }
+        });
+
+        try {
+            loginPage.open();
+            Assert.assertFalse(loginPage.isRememberMeChecked());
+            loginPage.setRememberMe(true);
+            Assert.assertTrue(loginPage.isRememberMeChecked());
+            loginPage.login("login-test", "password");
+
+            Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+            Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+            Event loginEvent = events.expectLogin().user(userId)
+                    .detail(Details.USERNAME, "login-test")
+                    .detail(Details.REMEMBER_ME, "true")
+                    .assertEvent();
+            String sessionId = loginEvent.getSessionId();
+
+            // Expire session
+            keycloakRule.removeUserSession(sessionId);
+
+            // Assert rememberMe checked and username/email prefilled
+            loginPage.open();
+            Assert.assertTrue(loginPage.isRememberMeChecked());
+            Assert.assertEquals("login-test", loginPage.getUsername());
+
+            loginPage.setRememberMe(false);
+        } finally {
+            keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setRememberMe(false);
+                }
+            });
+        }
+    }
+
+    @Test
     public void loginCancel() {
         loginPage.open();
         loginPage.cancel();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
index 25e36a8..1766a87 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
@@ -44,6 +44,9 @@ public class LoginPage extends AbstractPage {
     @FindBy(id = "totp")
     private WebElement totp;
 
+    @FindBy(id = "rememberMe")
+    private WebElement rememberMe;
+
     @FindBy(name = "login")
     private WebElement submitButton;
 
@@ -119,6 +122,16 @@ public class LoginPage extends AbstractPage {
         recoverUsernameLink.click();
     }
 
+    public void setRememberMe(boolean enable) {
+        boolean current = rememberMe.isSelected();
+        if (current != enable) {
+            rememberMe.click();
+        }
+    }
+
+    public boolean isRememberMeChecked() {
+        return rememberMe.isSelected();
+    }
 
     @Override
     public void open() {