keycloak-aplcache

test sso idle, logout on idle

5/16/2014 12:10:14 AM

Details

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 7a567b3..453f550 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -5,6 +5,7 @@ import org.jboss.resteasy.spi.HttpResponse;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.RSATokenVerifier;
 import org.keycloak.VerificationException;
+import org.keycloak.audit.Audit;
 import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.models.AuthenticationLinkModel;
 import org.keycloak.models.ClientModel;
@@ -62,10 +63,28 @@ public class AuthenticationManager {
     }
 
     public static boolean isSessionValid(RealmModel realm, UserSessionModel session) {
+        if (session == null) return false;
         int currentTime = Time.currentTime();
-        return session == null || session.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() < currentTime || session.getStarted() + realm.getSsoSessionMaxLifespan() < currentTime;
+        int max = session.getStarted() + realm.getSsoSessionMaxLifespan();
+        boolean valid = session != null && session.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() > currentTime && max > currentTime;
+        return valid;
     }
 
+    public static void logout(RealmModel realm, UserSessionModel session, UriInfo uriInfo) {
+        if (session == null) return;
+        UserModel user = session.getUser();
+
+        logger.infov("Logging out: {0} ({1})", user.getLoginName(), session.getId());
+
+        realm.removeUserSession(session);
+        expireIdentityCookie(realm, uriInfo);
+        expireRememberMeCookie(realm, uriInfo);
+
+        new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user.getId(), session.getId());
+
+    }
+
+
     public AccessToken createIdentityToken(RealmModel realm, UserModel user, UserSessionModel session) {
         logger.info("createIdentityToken");
         AccessToken token = new AccessToken();
@@ -121,26 +140,26 @@ public class AuthenticationManager {
         return encodedToken;
     }
 
-    public void expireIdentityCookie(RealmModel realm, UriInfo uriInfo) {
+    public static void expireIdentityCookie(RealmModel realm, UriInfo uriInfo) {
         logger.debug("Expiring identity cookie");
         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) {
+    public static void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo) {
         logger.debug("Expiring remember me cookie");
         String path = getIdentityCookiePath(realm, uriInfo);
         String cookieName = KEYCLOAK_REMEMBER_ME;
         expireCookie(realm, cookieName, path, true);
     }
 
-    protected String getIdentityCookiePath(RealmModel realm, UriInfo uriInfo) {
+    protected static String getIdentityCookiePath(RealmModel realm, UriInfo uriInfo) {
         URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getName());
         return uri.getRawPath();
     }
 
-    public void expireCookie(RealmModel realm, String cookieName, String path, boolean httpOnly) {
+    public static void expireCookie(RealmModel realm, String cookieName, String path, boolean httpOnly) {
         logger.debugv("Expiring cookie: {0} path: {1}", cookieName, path);
         boolean secureOnly = !realm.isSslNotRequired();
         CookieHelper.addCookie(cookieName, "", path, null, "Expiring cookie", 0, secureOnly, httpOnly);
@@ -196,11 +215,8 @@ public class AuthenticationManager {
             }
 
             UserSessionModel session = realm.getUserSession(token.getSessionState());
-            int currentTime = Time.currentTime();
-            if (isSessionValid(realm, session)) {
-                if (session != null) {
-                    realm.removeUserSession(session);
-                }
+            if (!isSessionValid(realm, session)) {
+                if (session != null) logout(realm, session, uriInfo);
                 logger.info("User session not active");
                 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 94f134f..89adfaf 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -23,6 +23,7 @@ import org.keycloak.representations.RefreshToken;
 import org.keycloak.util.Time;
 
 import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.util.HashSet;
@@ -106,7 +107,7 @@ public class TokenManager {
         return code;
     }
 
-    public AccessToken refreshAccessToken(RealmModel realm, ClientModel client, String encodedRefreshToken, Audit audit) throws OAuthErrorException {
+    public AccessToken refreshAccessToken(UriInfo uriInfo, RealmModel realm, ClientModel client, String encodedRefreshToken, Audit audit) throws OAuthErrorException {
         JWSInput jws = new JWSInput(encodedRefreshToken);
         RefreshToken refreshToken = null;
         try {
@@ -138,10 +139,8 @@ public class TokenManager {
 
         UserSessionModel session = realm.getUserSession(refreshToken.getSessionState());
         int currentTime = Time.currentTime();
-        if (AuthenticationManager.isSessionValid(realm, session)) {
-            if (session != null) {
-                realm.removeUserSession(session);
-            }
+        if (!AuthenticationManager.isSessionValid(realm, session)) {
+            AuthenticationManager.logout(realm, session, uriInfo);
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
         }
 
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 17e257c..4c6139c 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,8 @@ public class RequiredActionsService {
             AuthenticationManager authManager = new AuthenticationManager(providerSession);
 
             UserSessionModel session = realm.getUserSession(accessCode.getSessionState());
-            if (AuthenticationManager.isSessionValid(realm, session)) {
+            if (!AuthenticationManager.isSessionValid(realm, session)) {
+                AuthenticationManager.logout(realm, session, uriInfo);
                 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 2746461..e9c2c80 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -296,13 +296,14 @@ public class TokenService {
         String refreshToken = form.getFirst(OAuth2Constants.REFRESH_TOKEN);
         AccessToken accessToken = null;
         try {
-            accessToken = tokenManager.refreshAccessToken(realm, client, refreshToken, audit);
+            accessToken = tokenManager.refreshAccessToken(uriInfo, realm, client, refreshToken, audit);
         } catch (OAuthErrorException e) {
             Map<String, String> error = new HashMap<String, String>();
             error.put(OAuth2Constants.ERROR, e.getError());
             if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
             audit.error(Errors.INVALID_TOKEN);
-            throw new BadRequestException("OAuth Error", e, Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
+            logger.error("OAuth Error", e);
+            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
         }
 
         AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
@@ -635,7 +636,8 @@ public class TokenService {
         }
 
         UserSessionModel session = realm.getUserSession(accessCode.getSessionState());
-        if (AuthenticationManager.isSessionValid(realm, session)) {
+        if (!AuthenticationManager.isSessionValid(realm, session)) {
+            AuthenticationManager.logout(realm, session, uriInfo);
             Map<String, String> res = new HashMap<String, String>();
             res.put(OAuth2Constants.ERROR, "invalid_grant");
             res.put(OAuth2Constants.ERROR_DESCRIPTION, "Session not active");
@@ -854,15 +856,7 @@ public class TokenService {
 
     private void logout(UserSessionModel session) {
         UserModel user = session.getUser();
-
-        logger.infov("Logging out: {0} ({1})", user.getLoginName(), session.getId());
-
-        realm.removeUserSession(session);
-        authManager.expireIdentityCookie(realm, uriInfo);
-        authManager.expireRememberMeCookie(realm, uriInfo);
-
-        resourceAdminManager.logoutUser(uriInfo.getRequestUri(), realm, user.getId(), session.getId());
-
+        authManager.logout(realm, session, uriInfo);
         audit.user(user).session(session).success();
     }
 
@@ -914,8 +908,8 @@ public class TokenService {
         }
 
         UserSessionModel session = realm.getUserSession(accessCodeEntry.getSessionState());
-        int currentTime = Time.currentTime();
-        if (AuthenticationManager.isSessionValid(realm, session)) {
+        if (!AuthenticationManager.isSessionValid(realm, session)) {
+            AuthenticationManager.logout(realm, session, uriInfo);
             audit.error(Errors.INVALID_CODE);
             return oauth.forwardToSecurityFailure("Session not active");
         }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index 46c7bb2..ba5aaab 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
@@ -28,6 +28,7 @@ import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
@@ -156,4 +157,67 @@ public class AdapterTest {
 
 
     }
+
+    @Test
+    public void testLoginSSOIdle() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        KeycloakSession session = keycloakRule.startSession();
+        RealmModel realm = session.getRealmByName("demo");
+        int originalIdle = realm.getSsoSessionIdleTimeout();
+        realm.setSsoSessionIdleTimeout(1);
+        keycloakRule.stopSession(session, true);
+
+        Thread.sleep(2000);
+
+
+        // test SSO
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+        session = keycloakRule.startSession();
+        realm = session.getRealmByName("demo");
+        realm.setSsoSessionIdleTimeout(originalIdle);
+        keycloakRule.stopSession(session, true);
+    }
+    @Test
+    public void testLoginSSOMax() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        KeycloakSession session = keycloakRule.startSession();
+        RealmModel realm = session.getRealmByName("demo");
+        int original = realm.getSsoSessionMaxLifespan();
+        realm.setSsoSessionMaxLifespan(1);
+        keycloakRule.stopSession(session, true);
+
+        Thread.sleep(2000);
+
+
+        // test SSO
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+        session = keycloakRule.startSession();
+        realm = session.getRealmByName("demo");
+        realm.setSsoSessionMaxLifespan(original);
+        keycloakRule.stopSession(session, true);
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
index b661790..cead905 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
@@ -29,8 +29,14 @@ import org.keycloak.OAuth2Constants;
 import org.keycloak.audit.Details;
 import org.keycloak.audit.Errors;
 import org.keycloak.audit.Event;
+import org.keycloak.models.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.provider.ProviderSession;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.RefreshToken;
+import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
@@ -41,9 +47,7 @@ import org.keycloak.testsuite.rule.WebRule;
 import org.keycloak.util.Time;
 import org.openqa.selenium.WebDriver;
 
-import static org.hamcrest.Matchers.allOf;
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
@@ -162,4 +166,131 @@ public class RefreshTokenTest {
         events.clear();
     }
 
+    @Test
+    public void testUserSessionRefreshAndIdle() throws Exception {
+        oauth.doLogin("test-user@localhost", "password");
+
+        Event loginEvent = events.expectLogin().assertEvent();
+
+        String sessionId = loginEvent.getSessionId();
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+
+        events.poll();
+
+        String refreshId = oauth.verifyRefreshToken(tokenResponse.getRefreshToken()).getId();
+
+        KeycloakSession session = keycloakRule.startSession();
+        RealmModel realm = session.getRealmByName("test");
+        UserSessionModel userSession = realm.getUserSession(sessionId);
+        int last = userSession.getLastSessionRefresh();
+        keycloakRule.stopSession(session, false);
+
+        Thread.sleep(2000);
+
+        tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
+
+        AccessToken refreshedToken = oauth.verifyToken(tokenResponse.getAccessToken());
+        RefreshToken refreshedRefreshToken = oauth.verifyRefreshToken(tokenResponse.getRefreshToken());
+
+        Assert.assertEquals(200, tokenResponse.getStatusCode());
+
+        session = keycloakRule.startSession();
+        realm = session.getRealmByName("test");
+        userSession = realm.getUserSession(sessionId);
+        int next = userSession.getLastSessionRefresh();
+        keycloakRule.stopSession(session, false);
+
+        // should not update last refresh because the access token interval is way less than idle timeout
+        Assert.assertEquals(last, next);
+
+
+
+        session = keycloakRule.startSession();
+        realm = session.getRealmByName("test");
+        int lastAccessTokenLifespan = realm.getAccessTokenLifespan();
+        realm.setAccessTokenLifespan(100000);
+        keycloakRule.stopSession(session, true);
+
+        Thread.sleep(2000);
+        tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
+
+        session = keycloakRule.startSession();
+        realm = session.getRealmByName("test");
+        userSession = realm.getUserSession(sessionId);
+        next = userSession.getLastSessionRefresh();
+        keycloakRule.stopSession(session, false);
+
+        // lastSEssionRefresh should be updated because access code lifespan is higher than sso idle timeout
+        Assert.assertThat(next, allOf(greaterThan(last), lessThan(last + 6)));
+
+        session = keycloakRule.startSession();
+        realm = session.getRealmByName("test");
+        int originalIdle = realm.getSsoSessionIdleTimeout();
+        realm.setSsoSessionIdleTimeout(1);
+        keycloakRule.stopSession(session, true);
+
+        events.clear();
+        Thread.sleep(2000);
+        tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
+
+        // test idle timeout
+        assertEquals(400, tokenResponse.getStatusCode());
+        assertNull(tokenResponse.getAccessToken());
+        assertNull(tokenResponse.getRefreshToken());
+
+        events.expectRefresh(refreshId, sessionId).error(Errors.INVALID_TOKEN);
+
+        session = keycloakRule.startSession();
+        realm = session.getRealmByName("test");
+        realm.setSsoSessionIdleTimeout(originalIdle);
+        realm.setAccessTokenLifespan(lastAccessTokenLifespan);
+        keycloakRule.stopSession(session, true);
+
+        events.clear();
+    }
+
+    @Test
+    public void refreshTokenUserSessionMaxLifespan() throws Exception {
+        oauth.doLogin("test-user@localhost", "password");
+
+        Event loginEvent = events.expectLogin().assertEvent();
+
+        String sessionId = loginEvent.getSessionId();
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+
+        events.poll();
+
+        String refreshId = oauth.verifyRefreshToken(tokenResponse.getRefreshToken()).getId();
+
+        KeycloakSession session = keycloakRule.startSession();
+        RealmModel realm = session.getRealmByName("test");
+        int maxLifespan = realm.getSsoSessionMaxLifespan();
+        realm.setSsoSessionMaxLifespan(1);
+        keycloakRule.stopSession(session, true);
+
+        Thread.sleep(1000);
+
+        tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
+
+        assertEquals(400, tokenResponse.getStatusCode());
+        assertNull(tokenResponse.getAccessToken());
+        assertNull(tokenResponse.getRefreshToken());
+
+        session = keycloakRule.startSession();
+        realm = session.getRealmByName("test");
+        realm.setSsoSessionMaxLifespan(maxLifespan);
+        keycloakRule.stopSession(session, true);
+
+
+        events.expectRefresh(refreshId, sessionId).error(Errors.INVALID_TOKEN);
+
+        events.clear();
+    }
+
+
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
index f0e37cc..9312eee 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
@@ -133,4 +133,17 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
         byte[] bytes = os.toByteArray();
         return JsonSerialization.readValue(bytes, RealmRepresentation.class);
     }
+
+    public KeycloakSession startSession() {
+        KeycloakSession session = server.getKeycloakSessionFactory().createSession();
+        session.getTransaction().begin();
+        return session;
+    }
+
+    public void stopSession(KeycloakSession session, boolean commit) {
+        if (commit) {
+            session.getTransaction().commit();
+        }
+        session.close();
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
index d04cc21..4096658 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
@@ -81,19 +81,6 @@ public class KeycloakRule extends AbstractKeycloakRule {
         }
     }
 
-    public KeycloakSession startSession() {
-        KeycloakSession session = server.getKeycloakSessionFactory().createSession();
-        session.getTransaction().begin();
-        return session;
-    }
-
-    public void stopSession(KeycloakSession session, boolean commit) {
-        if (commit) {
-            session.getTransaction().commit();
-        }
-        session.close();
-    }
-
     public void removeUserSession(String sessionId) {
         KeycloakSession keycloakSession = startSession();
         RealmModel realm = keycloakSession.getRealm("test");