From b42f765c2abdafd38da3b5908f0a9af1b6aea8c3 Mon Sep 17 00:00:00 2001 From: Martin Kanis Date: Thu, 5 Aug 2021 11:26:47 +0200 Subject: [PATCH] KEYCLOAK-18982 Token OIDC introspection endpoint should not update any of the timestamps --- .../AccessTokenIntrospectionProvider.java | 6 ++--- .../keycloak/protocol/oidc/TokenManager.java | 11 ++++----- .../OpenShiftTokenReviewEndpoint.java | 2 +- .../oauth/TokenIntrospectionTest.java | 24 +++++++++++++++++++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java index 1261aa4083..f8c4e4ba9d 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java @@ -18,7 +18,6 @@ package org.keycloak.protocol.oidc; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.keycloak.OAuthErrorException; import org.keycloak.TokenVerifier; import org.keycloak.common.VerificationException; import org.keycloak.crypto.SignatureProvider; @@ -32,7 +31,6 @@ import org.keycloak.util.JsonSerialization; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import java.io.IOException; /** * @author Pedro Igor @@ -80,7 +78,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi } } - protected AccessToken verifyAccessToken(String token) throws OAuthErrorException, IOException { + protected AccessToken verifyAccessToken(String token) { AccessToken accessToken; try { @@ -97,7 +95,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi RealmModel realm = this.session.getContext().getRealm(); - return tokenManager.checkTokenValidForIntrospection(session, realm, accessToken) ? accessToken : null; + return tokenManager.checkTokenValidForIntrospection(session, realm, accessToken, false) ? accessToken : null; } @Override 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 360229e310..8b01dae601 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -67,7 +67,6 @@ import org.keycloak.representations.RefreshToken; import org.keycloak.services.ErrorResponseException; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationSessionManager; -import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.UserSessionCrossDCManager; import org.keycloak.services.managers.UserSessionManager; import org.keycloak.services.resources.IdentityBrokerService; @@ -218,16 +217,16 @@ public class TokenManager { } /** - * Checks if the token is valid. Intended usage is for token introspection endpoints as the session last refresh - * is updated if the token was valid. This is used to keep the session alive when long lived tokens are used. + * Checks if the token is valid. Optionally the session last refresh and client session timestamp + * are updated if the token was valid. This is used to keep the session alive when long lived tokens are used. * * @param session * @param realm * @param token + * @param updateTimestamps * @return - * @throws OAuthErrorException */ - public boolean checkTokenValidForIntrospection(KeycloakSession session, RealmModel realm, AccessToken token) throws OAuthErrorException { + public boolean checkTokenValidForIntrospection(KeycloakSession session, RealmModel realm, AccessToken token, boolean updateTimestamps) { ClientModel client = realm.getClientByClientId(token.getIssuedFor()); if (client == null || !client.isEnabled()) { return false; @@ -283,7 +282,7 @@ public class TokenManager { return false; } - if (valid) { + if (updateTimestamps && valid) { int currentTime = Time.currentTime(); userSession.setLastSessionRefresh(currentTime); if (clientSession != null) { diff --git a/services/src/main/java/org/keycloak/protocol/openshift/OpenShiftTokenReviewEndpoint.java b/services/src/main/java/org/keycloak/protocol/openshift/OpenShiftTokenReviewEndpoint.java index 60d1a562c0..9a2a9e01fd 100644 --- a/services/src/main/java/org/keycloak/protocol/openshift/OpenShiftTokenReviewEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/openshift/OpenShiftTokenReviewEndpoint.java @@ -105,7 +105,7 @@ public class OpenShiftTokenReviewEndpoint implements OIDCExtProvider, Environmen error(401, Errors.INVALID_TOKEN, "Token verification failure"); } - if (!tokenManager.checkTokenValidForIntrospection(session, realm, token)) { + if (!tokenManager.checkTokenValidForIntrospection(session, realm, token, true)) { error(401, Errors.INVALID_TOKEN, "Token verification failure"); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java index 096921ec29..750cf2564d 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java @@ -314,6 +314,8 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest { AbstractOIDCScopeTest.assertScopes("openid email profile", rep.getScope()); } + + @Test public void testIntrospectAccessTokenES256() throws Exception { testIntrospectAccessToken(Algorithm.ES256); @@ -398,6 +400,28 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest { assertEquals("test-app", rep.getClientId()); } + @Test + public void testIntrospectDoesntExtendTokenLifespan() throws Exception { + oauth.doLogin("test-user@localhost", "password"); + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password"); + accessTokenResponse = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken(), "password"); + + setTimeOffset(1200); + + String tokenResponse = oauth.introspectRefreshTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getRefreshToken()); + TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class); + + assertTrue(rep.isActive()); + assertEquals("test-user@localhost", rep.getUserName()); + assertEquals("test-app", rep.getClientId()); + + setTimeOffset(1200 + 1200); + + accessTokenResponse = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken(), "password"); + assertEquals(400, accessTokenResponse.getStatusCode()); + assertEquals("Token is not active", accessTokenResponse.getErrorDescription()); + } @Test public void testIntrospectAccessTokenUserDisabled() throws Exception {