mirror of
https://github.com/keycloak/keycloak.git
synced 2026-04-27 10:31:19 -05:00
Use offline time calculations when transient created from offline
Closes #40611 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
@@ -1037,8 +1037,11 @@ public class TokenManager {
|
||||
expiration = Time.currentTimeMillis() + TimeUnit.SECONDS.toMillis(tokenLifespan);
|
||||
}
|
||||
|
||||
final boolean offline = userSession.isOffline() || offlineTokenRequested ||
|
||||
(userSession.getPersistenceState() == UserSessionModel.SessionPersistenceState.TRANSIENT &&
|
||||
Constants.CREATED_FROM_PERSISTENT_OFFLINE.equals(userSession.getNote(Constants.CREATED_FROM_PERSISTENT)));
|
||||
long sessionExpires = SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(
|
||||
userSession.isOffline() || offlineTokenRequested, userSession.isRememberMe(),
|
||||
offline, userSession.isRememberMe(),
|
||||
TimeUnit.SECONDS.toMillis(clientSession.getStarted()), TimeUnit.SECONDS.toMillis(userSession.getStarted()),
|
||||
realm, client);
|
||||
expiration = sessionExpires > 0? Math.min(expiration, sessionExpires) : expiration;
|
||||
|
||||
+35
-10
@@ -42,6 +42,7 @@ import org.keycloak.models.AccountRoles;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.encode.AccessTokenContext;
|
||||
@@ -65,6 +66,7 @@ import org.keycloak.testsuite.client.policies.AbstractClientPoliciesTest;
|
||||
import org.keycloak.testsuite.pages.ConsentPage;
|
||||
import org.keycloak.testsuite.services.clientpolicy.executor.TestRaiseExceptionExecutorFactory;
|
||||
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
||||
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
||||
import org.keycloak.testsuite.updaters.ProtocolMappersUpdater;
|
||||
import org.keycloak.testsuite.updaters.RoleScopeUpdater;
|
||||
import org.keycloak.testsuite.updaters.UserAttributeUpdater;
|
||||
@@ -77,9 +79,7 @@ import org.keycloak.testsuite.utils.tls.TLSUtils;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -88,7 +88,6 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
||||
import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientScopesConditionConfig;
|
||||
@@ -372,20 +371,25 @@ public class StandardTokenExchangeV2Test extends AbstractClientPoliciesTest {
|
||||
public void testTransientOfflineSessionForRequester() throws Exception {
|
||||
final RealmResource realm = adminClient.realm(TEST);
|
||||
final UserRepresentation john = ApiUtil.findUserByUsername(realm, "john");
|
||||
try (ClientAttributeUpdater clientUpdater2 = ClientAttributeUpdater.forClient(adminClient, TEST, "subject-client")
|
||||
.setOptionalClientScopes(List.of(OAuth2Constants.OFFLINE_ACCESS))
|
||||
.update();
|
||||
) {
|
||||
try (RealmAttributeUpdater realUpdater = new RealmAttributeUpdater(realm)
|
||||
.setSsoSessionMaxLifespan(600)
|
||||
.update();
|
||||
ClientAttributeUpdater clientUpdater = ClientAttributeUpdater.forClient(adminClient, TEST, "subject-client")
|
||||
.setOptionalClientScopes(List.of(OAuth2Constants.OFFLINE_ACCESS))
|
||||
.update()) {
|
||||
|
||||
// Login, which creates offline-session
|
||||
oauth.realm(TEST);
|
||||
final String accessToken = resourceOwnerLogin("john", "password", "subject-client", "secret", OAuth2Constants.OFFLINE_ACCESS).getAccessToken();
|
||||
final AccessTokenResponse initialResponse = resourceOwnerLogin("john", "password", "subject-client", "secret", OAuth2Constants.OFFLINE_ACCESS);
|
||||
String accessToken = initialResponse.getAccessToken();
|
||||
|
||||
// Regular token-exchange with the access token as requested_token_type
|
||||
oauth.scope(OAuth2Constants.SCOPE_OPENID); // add openid scope for the user-info request
|
||||
AccessTokenResponse response = tokenExchange(accessToken, "requester-client", "secret", null, null);
|
||||
assertEquals(OAuth2Constants.ACCESS_TOKEN_TYPE, response.getIssuedTokenType());
|
||||
final String exchangedTokenString = response.getAccessToken();
|
||||
final AccessToken exchangedToken = TokenVerifier.create(exchangedTokenString, AccessToken.class).parse().getToken();
|
||||
String exchangedTokenString = response.getAccessToken();
|
||||
AccessToken exchangedToken = TokenVerifier.create(exchangedTokenString, AccessToken.class).parse().getToken();
|
||||
assertTrue("Exchanged token is not active", exchangedToken.isActive());
|
||||
assertEquals(getSessionIdFromToken(accessToken), exchangedToken.getSessionId());
|
||||
assertEquals("requester-client", exchangedToken.getIssuedFor());
|
||||
assertAccessTokenContext(exchangedToken.getId(), AccessTokenContext.SessionType.OFFLINE_TRANSIENT_CLIENT,
|
||||
@@ -400,6 +404,27 @@ public class StandardTokenExchangeV2Test extends AbstractClientPoliciesTest {
|
||||
assertIntrospectSuccess(exchangedTokenString, "requester-client", "secret", john.getId());
|
||||
assertUserInfoSuccess(exchangedTokenString, "requester-client", "secret", john.getId());
|
||||
|
||||
// move time to be more than the normal expired session value, refresh and request another exchange
|
||||
setTimeOffset(610);
|
||||
final AccessTokenResponse refreshResponse = oauth.client("subject-client", "secret").scope(null)
|
||||
.refreshRequest(initialResponse.getRefreshToken()).send();
|
||||
assertNull("Error refreshing the initial token: " + refreshResponse.getErrorDescription(), refreshResponse.getError());
|
||||
accessToken = refreshResponse.getAccessToken();
|
||||
oauth.scope(OAuth2Constants.SCOPE_OPENID);
|
||||
response = tokenExchange(accessToken, "requester-client", "secret", null, null);
|
||||
assertNull("Error exchanging the token: " + response.getErrorDescription(), response.getError());
|
||||
exchangedTokenString = response.getAccessToken();
|
||||
exchangedToken = TokenVerifier.create(exchangedTokenString, AccessToken.class).parse().getToken();
|
||||
assertTrue("Exchanged token is not active", exchangedToken.isActive());
|
||||
assertEquals(getSessionIdFromToken(accessToken), exchangedToken.getSessionId());
|
||||
assertEquals("requester-client", exchangedToken.getIssuedFor());
|
||||
assertAccessTokenContext(exchangedToken.getId(), AccessTokenContext.SessionType.OFFLINE_TRANSIENT_CLIENT,
|
||||
AccessTokenContext.TokenType.REGULAR, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE);
|
||||
|
||||
// assert introspection and user-info works
|
||||
assertIntrospectSuccess(exchangedTokenString, "requester-client", "secret", john.getId());
|
||||
assertUserInfoSuccess(exchangedTokenString, "requester-client", "secret", john.getId());
|
||||
|
||||
// assert introspection and user-info fails with offline session deleted
|
||||
realm.deleteSession(getSessionIdFromToken(accessToken), true);
|
||||
assertIntrospectError(exchangedTokenString, "requester-client", "secret");
|
||||
|
||||
Reference in New Issue
Block a user