keycloak-memoizeit

Details

diff --git a/core/src/main/java/org/keycloak/representations/IDToken.java b/core/src/main/java/org/keycloak/representations/IDToken.java
index c5493f6..4d595f5 100755
--- a/core/src/main/java/org/keycloak/representations/IDToken.java
+++ b/core/src/main/java/org/keycloak/representations/IDToken.java
@@ -47,6 +47,8 @@ public class IDToken extends JsonWebToken {
     public static final String ADDRESS = "address";
     public static final String UPDATED_AT = "updated_at";
     public static final String CLAIMS_LOCALES = "claims_locales";
+    public static final String ACR = "acr";
+
     // NOTE!!!  WE used to use @JsonUnwrapped on a UserClaimSet object.  This screws up otherClaims and the won't work
     // anymore.  So don't have any @JsonUnwrapped!
     @JsonProperty(NONCE)
@@ -118,6 +120,9 @@ public class IDToken extends JsonWebToken {
     @JsonProperty(CLAIMS_LOCALES)
     protected String claimsLocales;
 
+    @JsonProperty(ACR)
+    protected String acr;
+
     public String getNonce() {
         return nonce;
     }
@@ -302,4 +307,11 @@ public class IDToken extends JsonWebToken {
         this.claimsLocales = claimsLocales;
     }
 
+    public String getAcr() {
+        return acr;
+    }
+
+    public void setAcr(String acr) {
+        this.acr = acr;
+    }
 }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java
index c7d6f25..ca6d7e6 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java
@@ -54,7 +54,7 @@ public class CookieAuthenticator implements Authenticator {
                 context.attempted();
             } else {
                 ClientSessionModel clientSession = context.getClientSession();
-                clientSession.setNote(AuthenticationManager.SKIP_AUTH_TIME_UPDATE, "true");
+                clientSession.setNote(AuthenticationManager.SSO_AUTH, "true");
 
                 context.setUser(authResult.getUser());
                 context.attachUserSession(authResult.getSession());
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 0cb4bbf..54bf9ae 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -524,6 +524,11 @@ public class TokenManager {
         token.issuer(clientSession.getNote(OIDCLoginProtocol.ISSUER));
         token.setNonce(clientSession.getNote(OIDCLoginProtocol.NONCE_PARAM));
 
+        // Best effort for "acr" value. Use 0 if clientSession was authenticated through cookie ( SSO )
+        // TODO: Add better acr support. See KEYCLOAK-3314
+        String acr = (AuthenticationManager.isSSOAuthentication(clientSession)) ? "0" : "1";
+        token.setAcr(acr);
+
         String authTime = session.getNote(AuthenticationManager.AUTH_TIME);
         if (authTime != null) {
             token.setAuthTime(Integer.parseInt(authTime));
@@ -673,6 +678,7 @@ public class TokenManager {
             idToken.setAuthTime(accessToken.getAuthTime());
             idToken.setSessionState(accessToken.getSessionState());
             idToken.expiration(accessToken.getExpiration());
+            idToken.setAcr(accessToken.getAcr());
             transformIDToken(session, idToken, realm, client, userSession.getUser(), userSession, clientSession);
             return this;
         }
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 4e6901f..b20b487 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -64,8 +64,8 @@ public class AuthenticationManager {
 
     // userSession note with authTime (time when authentication flow including requiredActions was finished)
     public static final String AUTH_TIME = "AUTH_TIME";
-    // clientSession note with flag that authTime update should be skipped
-    public static final String SKIP_AUTH_TIME_UPDATE = "SKIP_AUTH_TIME_UPDATE";
+    // clientSession note with flag that clientSession was authenticated through SSO cookie
+    public static final String SSO_AUTH = "SSO_AUTH";
 
     protected static ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
     public static final String FORM_USERNAME = "username";
@@ -410,9 +410,8 @@ public class AuthenticationManager {
         if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
         if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
 
-        // Update userSession note with authTime. But just if flag SKIP_AUTH_TIME_UPDATE is not set
-        String skipAuthTimeUpdate = clientSession.getNote(SKIP_AUTH_TIME_UPDATE);
-        if (skipAuthTimeUpdate == null || !Boolean.parseBoolean(skipAuthTimeUpdate)) {
+        // Update userSession note with authTime. But just if flag SSO_AUTH is not set
+        if (!isSSOAuthentication(clientSession)) {
             int authTime = Time.currentTime();
             userSession.setNote(AUTH_TIME, String.valueOf(authTime));
         }
@@ -420,6 +419,13 @@ public class AuthenticationManager {
         return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
 
     }
+
+    public static boolean isSSOAuthentication(ClientSessionModel clientSession) {
+        String ssoAuth = clientSession.getNote(SSO_AUTH);
+        return Boolean.parseBoolean(ssoAuth);
+    }
+
+
     public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
                                                   ClientConnection clientConnection,
                                                   HttpRequest request, UriInfo uriInfo, EventBuilder event) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/SSOTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/SSOTest.java
index 7a693c7..17cd717 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/SSOTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/SSOTest.java
@@ -23,6 +23,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.events.Details;
+import org.keycloak.representations.IDToken;
 import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.AssertEvents;
@@ -73,7 +74,11 @@ public class SSOTest extends TestRealmKeycloakTest {
         assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
         Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
 
-        String sessionId = events.expectLogin().assertEvent().getSessionId();
+        EventRepresentation loginEvent = events.expectLogin().assertEvent();
+        String sessionId = loginEvent.getSessionId();
+
+        IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent);
+        Assert.assertEquals("1", idToken.getAcr());
 
         appPage.open();
 
@@ -81,13 +86,17 @@ public class SSOTest extends TestRealmKeycloakTest {
 
         assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
-        profilePage.open();
+        loginEvent = events.expectLogin().removeDetail(Details.USERNAME).client("test-app").assertEvent();
+        String sessionId2 = loginEvent.getSessionId();
 
-        assertTrue(profilePage.isCurrent());
+        assertEquals(sessionId, sessionId2);
 
-        String sessionId2 = events.expectLogin().removeDetail(Details.USERNAME).client("test-app").assertEvent().getSessionId();
+        // acr is 0 as we authenticated through SSO cookie
+        idToken = sendTokenRequestAndGetIDToken(loginEvent);
+        Assert.assertEquals("0", idToken.getAcr());
 
-        assertEquals(sessionId, sessionId2);
+        profilePage.open();
+        assertTrue(profilePage.isCurrent());
 
         // Expire session
         testingClient.testing().removeUserSession("test", sessionId);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
index 16117c9..e6a72a0 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
@@ -31,24 +31,25 @@ import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.Assert;
 import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.TestRealmKeycloakTest;
 import org.keycloak.testsuite.admin.AbstractAdminTest;
 import org.keycloak.testsuite.util.ClientManager;
 import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.RealmBuilder;
 
 /**
  * Test for supporting advanced parameters of OIDC specs (max_age, nonce, prompt, ...)
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-public class OIDCAdvancedRequestParamsTest extends AbstractKeycloakTest {
+public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest {
 
     @Rule
     public AssertEvents events = new AssertEvents(this);
 
 
     @Override
-    public void beforeAbstractKeycloakTest() throws Exception {
-        super.beforeAbstractKeycloakTest();
+    public void configureTestRealm(RealmRepresentation testRealm) {
     }
 
     @Before
@@ -76,7 +77,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractKeycloakTest {
         oauth.doLogin("test-user@localhost", "password");
         EventRepresentation loginEvent = events.expectLogin().assertEvent();
 
-        IDToken idToken = retrieveIDToken(loginEvent);
+        IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent);
 
         // Check that authTime is available and set to current time
         int authTime = idToken.getAuthTime();
@@ -93,7 +94,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractKeycloakTest {
         oauth.doLogin("test-user@localhost", "password");
         loginEvent = events.expectLogin().assertEvent();
 
-        idToken = retrieveIDToken(loginEvent);
+        idToken = sendTokenRequestAndGetIDToken(loginEvent);
 
         // Assert that authTime was updated
         int authTimeUpdated = idToken.getAuthTime();
@@ -106,7 +107,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractKeycloakTest {
         oauth.doLogin("test-user@localhost", "password");
         EventRepresentation loginEvent = events.expectLogin().assertEvent();
 
-        IDToken idToken = retrieveIDToken(loginEvent);
+        IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent);
 
         // Check that authTime is available and set to current time
         int authTime = idToken.getAuthTime();
@@ -123,25 +124,11 @@ public class OIDCAdvancedRequestParamsTest extends AbstractKeycloakTest {
         oauth.openLoginForm();
         loginEvent = events.expectLogin().assertEvent();
 
-        idToken = retrieveIDToken(loginEvent);
+        idToken = sendTokenRequestAndGetIDToken(loginEvent);
 
         // Assert that authTime is still the same
         int authTimeUpdated = idToken.getAuthTime();
         Assert.assertEquals(authTime, authTimeUpdated);
     }
 
-    private IDToken retrieveIDToken(EventRepresentation loginEvent) {
-        String sessionId = loginEvent.getSessionId();
-        String codeId = loginEvent.getDetails().get(Details.CODE_ID);
-
-        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
-        OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
-
-        Assert.assertEquals(200, response.getStatusCode());
-        IDToken idToken = oauth.verifyIDToken(response.getIdToken());
-
-        events.expectCodeToToken(codeId, sessionId).assertEvent();
-        return idToken;
-    }
-
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/TestRealmKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/TestRealmKeycloakTest.java
index 4eaba86..0e10c58 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/TestRealmKeycloakTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/TestRealmKeycloakTest.java
@@ -17,12 +17,19 @@
 
 package org.keycloak.testsuite;
 
+import org.keycloak.OAuth2Constants;
+import org.keycloak.common.util.reflections.Reflections;
+import org.keycloak.events.Details;
+import org.keycloak.representations.IDToken;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 
+import java.lang.reflect.Field;
 import java.util.List;
 import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.util.OAuthClient;
 
 import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
 
@@ -74,4 +81,23 @@ public abstract class TestRealmKeycloakTest extends AbstractKeycloakTest {
      */
     public abstract void configureTestRealm(RealmRepresentation testRealm);
 
+
+    protected IDToken sendTokenRequestAndGetIDToken(EventRepresentation loginEvent) {
+        String sessionId = loginEvent.getSessionId();
+        String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+
+        Assert.assertEquals(200, response.getStatusCode());
+        IDToken idToken = oauth.verifyIDToken(response.getIdToken());
+
+        Field eventsField = Reflections.findDeclaredField(this.getClass(), "events");
+        if (eventsField != null) {
+            AssertEvents events = Reflections.getFieldValue(eventsField, this, AssertEvents.class);
+            events.expectCodeToToken(codeId, sessionId).assertEvent();
+        }
+        return idToken;
+    }
+
 }