keycloak-aplcache

sso session idle and max lifespan

5/15/2014 6:25:57 PM

Details

diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
index a2e7979..7313b5c 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
@@ -121,7 +121,7 @@ public class FreeMarkerAccount implements Account {
                 attributes.put("log", new LogBean(events));
                 break;
             case SESSIONS:
-                attributes.put("sessions", new SessionsBean(sessions));
+                attributes.put("sessions", new SessionsBean(realm, sessions));
                 break;
         }
 
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java
old mode 100644
new mode 100755
index 4db66dd..173414e
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java
@@ -1,5 +1,6 @@
 package org.keycloak.account.freemarker.model;
 
+import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.util.Time;
 
@@ -13,11 +14,12 @@ import java.util.List;
 public class SessionsBean {
 
     private List<UserSessionBean> events;
+    private RealmModel realm;
 
-    public SessionsBean(List<UserSessionModel> sessions) {
+    public SessionsBean(RealmModel realm, List<UserSessionModel> sessions) {
         this.events = new LinkedList<UserSessionBean>();
         for (UserSessionModel session : sessions) {
-            this.events.add(new UserSessionBean(session));
+            this.events.add(new UserSessionBean(realm, session));
         }
     }
 
@@ -28,8 +30,10 @@ public class SessionsBean {
     public static class UserSessionBean {
 
         private UserSessionModel session;
+        private RealmModel realm;
 
-        public UserSessionBean(UserSessionModel session) {
+        public UserSessionBean(RealmModel realm, UserSessionModel session) {
+            this.realm = realm;
             this.session = session;
         }
 
@@ -42,7 +46,8 @@ public class SessionsBean {
         }
 
         public Date getExpires() {
-            return Time.toDate(session.getExpires());
+            int max = session.getStarted() + realm.getSsoSessionMaxLifespan();
+            return Time.toDate(max);
         }
 
     }
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
old mode 100644
new mode 100755
index 33c33fc..e2e16a7
--- a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -21,8 +21,8 @@ public interface UserSessionModel {
 
     void setStarted(int started);
 
-    int getExpires();
+    int getLastSessionRefresh();
 
-    void setExpires(int expires);
+    void setLastSessionRefresh(int seconds);
 
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
old mode 100644
new mode 100755
index 83db701..62769ea
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
@@ -17,7 +17,7 @@ import javax.persistence.NamedQuery;
 @NamedQueries({
         @NamedQuery(name = "getUserSessionByUser", query = "select s from UserSessionEntity s where s.user = :user"),
         @NamedQuery(name = "removeUserSessionByUser", query = "delete from UserSessionEntity s where s.user = :user"),
-        @NamedQuery(name = "removeUserSessionExpired", query = "delete from UserSessionEntity s where s.expires < :currentTime")
+        @NamedQuery(name = "removeUserSessionExpired", query = "delete from UserSessionEntity s where s.started < :maxTime or s.lastSessionRefresh < :idleTime")
 })
 public class UserSessionEntity {
 
@@ -33,7 +33,7 @@ public class UserSessionEntity {
 
     int started;
 
-    int expires;
+    int lastSessionRefresh;
 
     public String getId() {
         return id;
@@ -67,12 +67,11 @@ public class UserSessionEntity {
         this.started = started;
     }
 
-    public int getExpires() {
-        return expires;
+    public int getLastSessionRefresh() {
+        return lastSessionRefresh;
     }
 
-    public void setExpires(int expires) {
-        this.expires = expires;
+    public void setLastSessionRefresh(int lastSessionRefresh) {
+        this.lastSessionRefresh = lastSessionRefresh;
     }
-
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 1ac2b6d..3f420c8 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1389,10 +1389,9 @@ public class RealmAdapter implements RealmModel {
         entity.setIpAddress(ipAddress);
 
         int currentTime = Time.currentTime();
-        int expires = currentTime + realm.getSsoSessionIdleTimeout();
 
         entity.setStarted(currentTime);
-        entity.setExpires(expires);
+        entity.setLastSessionRefresh(currentTime);
 
         em.persist(entity);
         return new UserSessionAdapter(entity);
@@ -1429,7 +1428,10 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public void removeExpiredUserSessions() {
-        em.createNamedQuery("removeUserSessionExpired").setParameter("currentTime", Time.currentTime()).executeUpdate();
+        em.createNamedQuery("removeUserSessionExpired")
+                .setParameter("maxTime", Time.currentTime() - getSsoSessionMaxLifespan())
+                .setParameter("idleTime", Time.currentTime() - getSsoSessionIdleTimeout())
+                .executeUpdate();
     }
 
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java
old mode 100644
new mode 100755
index 1f4ba28..0de48e5
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java
@@ -60,13 +60,12 @@ public class UserSessionAdapter implements UserSessionModel {
     }
 
     @Override
-    public int getExpires() {
-        return entity.getExpires();
+    public int getLastSessionRefresh() {
+        return entity.getLastSessionRefresh();
     }
 
     @Override
-    public void setExpires(int expires) {
-        entity.setExpires(expires);
+    public void setLastSessionRefresh(int seconds) {
+        entity.setLastSessionRefresh(seconds);
     }
-
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index d9590bb..eb61b5b 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -1356,10 +1356,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         entity.setIpAddress(ipAddress);
 
         int currentTime = Time.currentTime();
-        int expires = currentTime + realm.getSsoSessionIdleTimeout();
 
         entity.setStarted(currentTime);
-        entity.setExpires(expires);
+        entity.setLastSessionRefresh(currentTime);
 
         getMongoStore().insertEntity(entity, invocationContext);
         return new UserSessionAdapter(entity, this, invocationContext);
@@ -1398,8 +1397,14 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
 
     @Override
     public void removeExpiredUserSessions() {
+        int currentTime = Time.currentTime();
         DBObject query = new QueryBuilder()
-                .and("expires").lessThan(Time.currentTime())
+                .and("started").lessThan(currentTime - realm.getSsoSessionMaxLifespan())
+                .get();
+
+        getMongoStore().removeEntities(MongoUserSessionEntity.class, query, invocationContext);
+        query = new QueryBuilder()
+                .and("lastSessionRefresh").lessThan(currentTime - realm.getSsoSessionIdleTimeout())
                 .get();
 
         getMongoStore().removeEntities(MongoUserSessionEntity.class, query, invocationContext);
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java
old mode 100644
new mode 100755
index ab02e1c..675e4ba
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserSessionAdapter.java
@@ -65,13 +65,13 @@ public class UserSessionAdapter implements UserSessionModel {
     }
 
     @Override
-    public int getExpires() {
-        return entity.getExpires();
+    public int getLastSessionRefresh() {
+        return entity.getLastSessionRefresh();
     }
 
     @Override
-    public void setExpires(int expires) {
-        entity.setExpires(expires);
+    public void setLastSessionRefresh(int seconds) {
+        entity.setLastSessionRefresh(seconds);
     }
 
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java
old mode 100644
new mode 100755
index de2a6d0..2d98d19
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java
@@ -17,7 +17,7 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
 
     private int started;
 
-    private int expires;
+    private int lastSessionRefresh;
 
     public String getUser() {
         return user;
@@ -43,12 +43,12 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
         this.started = started;
     }
 
-    public int getExpires() {
-        return expires;
+    public int getLastSessionRefresh() {
+        return lastSessionRefresh;
     }
 
-    public void setExpires(int expires) {
-        this.expires = expires;
+    public void setLastSessionRefresh(int lastSessionRefresh) {
+        this.lastSessionRefresh = lastSessionRefresh;
     }
 
     @Override
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 41586d6..f67357e 100755
--- a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
@@ -2,22 +2,13 @@ package org.keycloak.services.managers;
 
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.UnauthorizedException;
-import org.keycloak.RSATokenVerifier;
-import org.keycloak.VerificationException;
-import org.keycloak.jose.jws.JWSInput;
-import org.keycloak.jose.jws.crypto.RSAProvider;
-import org.keycloak.models.ClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.provider.ProviderSession;
-import org.keycloak.representations.AccessToken;
 
-import org.jboss.resteasy.spi.BadRequestException;
 import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.NewCookie;
 import javax.ws.rs.core.UriInfo;
-import java.net.URI;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -33,6 +24,11 @@ public class AppAuthManager extends AuthenticationManager {
     public UserModel authenticateRequest(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
         AuthResult authResult = authenticateIdentityCookie(realm, uriInfo, headers);
         if (authResult != null) {
+            Cookie remember = headers.getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
+            boolean rememberMe = remember != null;
+            // refresh the cookies!
+            createLoginCookie(realm, authResult.getUser(), authResult.getSession(), uriInfo, rememberMe);
+            if (rememberMe) createRememberMeCookie(realm, uriInfo);
             return authResult.getUser();
         } else {
             return authenticateBearerToken(realm, uriInfo, headers);
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 e6816c0..7a567b3 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -61,6 +61,11 @@ public class AuthenticationManager {
         this.protector = protector;
     }
 
+    public static boolean isSessionValid(RealmModel realm, UserSessionModel session) {
+        int currentTime = Time.currentTime();
+        return session == null || session.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() < currentTime || session.getStarted() + realm.getSsoSessionMaxLifespan() < currentTime;
+    }
+
     public AccessToken createIdentityToken(RealmModel realm, UserModel user, UserSessionModel session) {
         logger.info("createIdentityToken");
         AccessToken token = new AccessToken();
@@ -77,7 +82,7 @@ public class AuthenticationManager {
         return token;
     }
 
-    public void createLoginCookie(Response.ResponseBuilder builder, RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, boolean rememberMe) {
+    public void createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, boolean rememberMe) {
         logger.info("createLoginCookie");
         String cookiePath = getIdentityCookiePath(realm, uriInfo);
         AccessToken identityToken = createIdentityToken(realm, user, session);
@@ -97,11 +102,11 @@ public class AuthenticationManager {
             sessionCookieValue += "-" + session.getId();
         }
         // THIS SHOULD NOT BE A HTTPONLY COOKIE!  It is used for OpenID Connect Iframe Session support!
-        builder.cookie(new NewCookie(KEYCLOAK_SESSION_COOKIE, sessionCookieValue, cookiePath, null, null, maxAge, secureOnly));
+        CookieHelper.addCookie(KEYCLOAK_SESSION_COOKIE, sessionCookieValue, cookiePath, null, null, maxAge, secureOnly, false);
 
     }
 
-    public void createRememberMeCookie(HttpResponse response, RealmModel realm, UriInfo uriInfo) {
+    public void createRememberMeCookie(RealmModel realm, UriInfo uriInfo) {
         String path = getIdentityCookiePath(realm, uriInfo);
         boolean secureOnly = !realm.isSslNotRequired();
         // remember me cookie should be persistent
@@ -121,6 +126,7 @@ public class AuthenticationManager {
         String path = getIdentityCookiePath(realm, uriInfo);
         expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true);
         expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false);
+        expireRememberMeCookie(realm, uriInfo);
     }
     public void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo) {
         logger.debug("Expiring remember me cookie");
@@ -146,15 +152,9 @@ public class AuthenticationManager {
 
     public AuthResult authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, boolean checkActive) {
         logger.info("authenticateIdentityCookie");
-        String cookieName = KEYCLOAK_IDENTITY_COOKIE;
-        return authenticateIdentityCookie(realm, uriInfo, headers, cookieName, checkActive);
-    }
-
-    private AuthResult authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName, boolean checkActive) {
-        logger.info("authenticateIdentityCookie");
-        Cookie cookie = headers.getCookies().get(cookieName);
+        Cookie cookie = headers.getCookies().get(KEYCLOAK_IDENTITY_COOKIE);
         if (cookie == null) {
-            logger.infov("authenticateCookie could not find cookie: {0}", cookieName);
+            logger.infov("authenticateCookie could not find cookie: {0}", KEYCLOAK_IDENTITY_COOKIE);
             return null;
         }
 
@@ -162,7 +162,9 @@ public class AuthenticationManager {
         AuthResult authResult = verifyIdentityToken(realm, uriInfo, checkActive, tokenString);
         if (authResult == null) {
             expireIdentityCookie(realm, uriInfo);
+            return null;
         }
+        authResult.getSession().setLastSessionRefresh(Time.currentTime());
         return authResult;
     }
 
@@ -174,7 +176,6 @@ public class AuthenticationManager {
                 logger.info("Checking if identity token is active");
                 if (!token.isActive() || token.getIssuedAt() < realm.getNotBefore()) {
                     logger.info("identity cookie expired");
-                    expireIdentityCookie(realm, uriInfo);
                     return null;
                 } else {
                     logger.info("token.isActive() : " + token.isActive());
@@ -195,9 +196,12 @@ public class AuthenticationManager {
             }
 
             UserSessionModel session = realm.getUserSession(token.getSessionState());
-            if (session == null || session.getExpires() < Time.currentTime()) {
+            int currentTime = Time.currentTime();
+            if (isSessionValid(realm, session)) {
+                if (session != null) {
+                    realm.removeUserSession(session);
+                }
                 logger.info("User session not active");
-                expireIdentityCookie(realm, uriInfo);
                 return null;
             }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
index 9e4b60a..94f134f 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -137,7 +137,11 @@ public class TokenManager {
         }
 
         UserSessionModel session = realm.getUserSession(refreshToken.getSessionState());
-        if (session == null || session.getExpires() < Time.currentTime()) {
+        int currentTime = Time.currentTime();
+        if (AuthenticationManager.isSessionValid(realm, session)) {
+            if (session != null) {
+                realm.removeUserSession(session);
+            }
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
         }
 
@@ -191,6 +195,12 @@ public class TokenManager {
         AccessToken accessToken = initToken(realm, client, user, session);
         accessToken.setRealmAccess(refreshToken.getRealmAccess());
         accessToken.setResourceAccess(refreshToken.getResourceAccess());
+
+        // only refresh session if next token refresh will be after idle timeout
+        if (currentTime + realm.getAccessTokenLifespan() > session.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout()) {
+            session.setLastSessionRefresh(currentTime);
+        }
+
         return accessToken;
     }
 
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 d436318..d31aab5 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
@@ -93,7 +93,9 @@ public class OAuthFlows {
             Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
             Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
             rememberMe = rememberMe || remember != null;
-            authManager.createLoginCookie(location, realm, accessCode.getUser(), session, uriInfo, rememberMe);
+            // refresh the cookies!
+            authManager.createLoginCookie(realm, accessCode.getUser(), session, uriInfo, rememberMe);
+            if (rememberMe) authManager.createRememberMeCookie(realm, uriInfo);
             return location.build();
         }
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
index e760263..17e257c 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -408,7 +408,7 @@ public class RequiredActionsService {
             AuthenticationManager authManager = new AuthenticationManager(providerSession);
 
             UserSessionModel session = realm.getUserSession(accessCode.getSessionState());
-            if (session == null || session.getExpires() < Time.currentTime()) {
+            if (AuthenticationManager.isSessionValid(realm, session)) {
                 return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectError(accessCode.getClient(), "access_denied", accessCode.getState(), accessCode.getRedirectUri());
             }
             audit.session(session);
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 a262c83..2746461 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -58,7 +58,6 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.NewCookie;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.SecurityContext;
 import javax.ws.rs.core.UriBuilder;
@@ -374,7 +373,7 @@ public class TokenService {
         AuthenticationStatus status = authManager.authenticateForm(clientConnection, realm, formData);
 
         if (remember) {
-            authManager.createRememberMeCookie(response, realm, uriInfo);
+            authManager.createRememberMeCookie(realm, uriInfo);
         } else {
             authManager.expireRememberMeCookie(realm, uriInfo);
         }
@@ -636,7 +635,7 @@ public class TokenService {
         }
 
         UserSessionModel session = realm.getUserSession(accessCode.getSessionState());
-        if (session == null || session.getExpires() < Time.currentTime()) {
+        if (AuthenticationManager.isSessionValid(realm, session)) {
             Map<String, String> res = new HashMap<String, String>();
             res.put(OAuth2Constants.ERROR, "invalid_grant");
             res.put(OAuth2Constants.ERROR_DESCRIPTION, "Session not active");
@@ -915,7 +914,8 @@ public class TokenService {
         }
 
         UserSessionModel session = realm.getUserSession(accessCodeEntry.getSessionState());
-        if (session == null || session.getExpires() < Time.currentTime()) {
+        int currentTime = Time.currentTime();
+        if (AuthenticationManager.isSessionValid(realm, session)) {
             audit.error(Errors.INVALID_CODE);
             return oauth.forwardToSecurityFailure("Session not active");
         }