keycloak-memoizeit
Changes
services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/SSOTest.java 19(+14 -5)
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;
+ }
+
}