KEYCLOAK-13844 "exp" claim should not be "0" when using offline token

This commit is contained in:
Yoshiyuki Tabata 2020-04-24 10:54:42 +09:00 committed by Marek Posolda
parent 3291161954
commit f7d00fc2e9
2 changed files with 85 additions and 6 deletions

View file

@ -648,12 +648,16 @@ public class TokenManager {
token.setSessionState(session.getId());
token.expiration(getTokenExpiration(realm, client, session, clientSession));
ClientScopeModel offlineAccessScope = KeycloakModelUtils.getClientScopeByName(realm, OAuth2Constants.OFFLINE_ACCESS);
boolean offlineTokenRequested = offlineAccessScope == null ? false
: clientSessionCtx.getClientScopeIds().contains(offlineAccessScope.getId());
token.expiration(getTokenExpiration(realm, client, session, clientSession, offlineTokenRequested));
return token;
}
private int getTokenExpiration(RealmModel realm, ClientModel client, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
private int getTokenExpiration(RealmModel realm, ClientModel client, UserSessionModel userSession,
AuthenticatedClientSessionModel clientSession, boolean offlineTokenRequested) {
boolean implicitFlow = false;
String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
if (responseType != null) {
@ -681,9 +685,16 @@ public class TokenManager {
expiration = Time.currentTime() + tokenLifespan;
}
if (!userSession.isOffline()) {
int sessionExpires = userSession.getStarted() + (userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ?
realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan());
if (userSession.isOffline() || offlineTokenRequested) {
if (realm.isOfflineSessionMaxLifespanEnabled()) {
int sessionExpires = userSession.getStarted() + realm.getOfflineSessionMaxLifespan();
expiration = expiration <= sessionExpires ? expiration : sessionExpires;
}
} else {
int sessionExpires = userSession.getStarted()
+ (userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0
? realm.getSsoSessionMaxLifespanRememberMe()
: realm.getSsoSessionMaxLifespan());
expiration = expiration <= sessionExpires ? expiration : sessionExpires;
}
@ -779,6 +790,8 @@ public class TokenManager {
refreshToken = new RefreshToken(accessToken);
refreshToken.type(TokenUtil.TOKEN_TYPE_OFFLINE);
if (realm.isOfflineSessionMaxLifespanEnabled())
refreshToken.expiration(getOfflineExpiration());
sessionManager.createOrUpdateOfflineSession(clientSessionCtx.getClientSession(), userSession);
} else {
refreshToken = new RefreshToken(accessToken);
@ -828,6 +841,13 @@ public class TokenManager {
return expiration <= sessionExpires ? expiration : sessionExpires;
}
private int getOfflineExpiration() {
int expiration = Time.currentTime() + realm.getOfflineSessionIdleTimeout();
int sessionExpires = userSession.getStarted() + realm.getOfflineSessionMaxLifespan();
return expiration <= sessionExpires ? expiration : sessionExpires;
}
public AccessTokenResponseBuilder generateIDToken() {
if (accessToken == null) {
throw new IllegalStateException("accessToken not set");

View file

@ -70,6 +70,9 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertFalse;
@ -81,6 +84,9 @@ import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsernameId;
import static org.keycloak.testsuite.util.OAuthClient.APP_ROOT;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@ -557,7 +563,9 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
// Login as admin and see consents of user
UserResource user = ApiUtil.findUserByUsernameId(appRealm, "test-user@localhost");
List<Map<String, Object>> consents = user.getConsents();
assertTrue(consents.isEmpty());
for (Map<String, Object> consent : consents) {
assertNotEquals(consent.get("clientId"), "offline-client-2");
}
}
@Test
@ -657,6 +665,17 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
return prev;
}
private int[] changeSessionSettings(int ssoSessionIdle, int accessTokenLifespan) {
int prev[] = new int[2];
RealmRepresentation rep = adminClient.realm("test").toRepresentation();
prev[0] = rep.getOfflineSessionMaxLifespan().intValue();
prev[1] = rep.getOfflineSessionIdleTimeout().intValue();
RealmBuilder realmBuilder = RealmBuilder.create();
realmBuilder.ssoSessionIdleTimeout(ssoSessionIdle).accessTokenLifespan(accessTokenLifespan);
adminClient.realm("test").update(realmBuilder.build());
return prev;
}
@Test
public void offlineTokenBrowserFlowMaxLifespanExpired() throws Exception {
// expect that offline session expired by max lifespan
@ -822,4 +841,44 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
}
@Test
public void testShortOfflineSessionMax() throws Exception {
int prevOfflineSession[] = null;
int prevSession[] = null;
try {
prevOfflineSession = changeOfflineSessionSettings(true, 60, 30);
prevSession = changeSessionSettings(1800, 300);
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
oauth.clientId("offline-client");
oauth.redirectUri(offlineClientAppUri);
oauth.doLogin("test-user@localhost", "password");
events.expectLogin().client("offline-client").detail(Details.REDIRECT_URI, offlineClientAppUri).assertEvent();
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "secret1");
String offlineTokenString = tokenResponse.getRefreshToken();
RefreshToken offlineToken = oauth.parseRefreshToken(offlineTokenString);
Assert.assertThat(tokenResponse.getExpiresIn(), allOf(greaterThanOrEqualTo(59), lessThanOrEqualTo(60)));
Assert.assertThat(tokenResponse.getRefreshExpiresIn(), allOf(greaterThanOrEqualTo(29), lessThanOrEqualTo(30)));
assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
String introspectionResponse = oauth.introspectAccessTokenWithClientCredential("test-app", "password",
tokenResponse.getAccessToken());
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(introspectionResponse);
Assert.assertEquals(true, jsonNode.get("active").asBoolean());
Assert.assertEquals("test-user@localhost", jsonNode.get("email").asText());
Assert.assertThat(jsonNode.get("exp").asInt() - getCurrentTime(),
allOf(greaterThanOrEqualTo(59), lessThanOrEqualTo(60)));
} finally {
changeOfflineSessionSettings(false, prevOfflineSession[0], prevOfflineSession[1]);
changeSessionSettings(prevSession[0], prevSession[1]);
}
}
}