From 55c5e9a3812b899d6986c876a862b58e8b09ddfe Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 5 Apr 2016 09:31:39 +0200 Subject: [PATCH] KEYCLOAK-2722 Check user session in token introspection endpoint --- .../keycloak/protocol/oidc/TokenManager.java | 23 +++++ .../endpoints/TokenIntrospectionEndpoint.java | 6 +- .../resources/admin/UsersResource.java | 2 +- .../oauth/TokenIntrospectionTest.java | 89 +++++++++++++++++++ 4 files changed, 117 insertions(+), 3 deletions(-) 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 e580af6fcc..bbd41c0a2d 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -174,8 +174,31 @@ public class TokenManager { verifyAccess(oldToken, newToken); return new TokenValidation(user, userSession, clientSession, newToken); + } + public boolean isTokenValid(KeycloakSession session, RealmModel realm, AccessToken token) throws OAuthErrorException { + if (!token.isActive()) { + return false; + } + if (token.getIssuedAt() < realm.getNotBefore()) { + return false; + } + + UserModel user = session.users().getUserById(token.getSubject(), realm); + if (user == null) { + return false; + } + if (!user.isEnabled()) { + return false; + } + + UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState()); + if (!AuthenticationManager.isSessionValid(realm, userSession)) { + return false; + } + + return true; } public RefreshResult refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event, HttpHeaders headers) throws OAuthErrorException { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenIntrospectionEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenIntrospectionEndpoint.java index 9d28807e04..323a6ff331 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenIntrospectionEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenIntrospectionEndpoint.java @@ -31,6 +31,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.protocol.oidc.TokenManager; +import org.keycloak.protocol.oidc.TokenManager.TokenValidation; import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil; import org.keycloak.representations.AccessToken; import org.keycloak.services.ErrorResponseException; @@ -106,7 +107,8 @@ public class TokenIntrospectionEndpoint { AccessToken toIntrospect = toAccessToken(tokenTypeHint, token); ObjectNode tokenMetadata; - if (toIntrospect.isActive()) { + boolean active = tokenManager.isTokenValid(session, realm, toIntrospect); + if (active) { tokenMetadata = JsonSerialization.createObjectNode(toIntrospect); tokenMetadata.put("client_id", toIntrospect.getIssuedFor()); tokenMetadata.put("username", toIntrospect.getPreferredUsername()); @@ -114,7 +116,7 @@ public class TokenIntrospectionEndpoint { tokenMetadata = JsonSerialization.createObjectNode(); } - tokenMetadata.put("active", toIntrospect.isActive()); + tokenMetadata.put("active", active); this.event.success(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 0d7c8902e7..5e37c1e80a 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -166,7 +166,7 @@ public class UsersResource { attrsToRemove = Collections.emptySet(); } - if (rep.isEnabled() != null && rep.isEnabled()) { + if (rep.isEnabled() != null && rep.isEnabled() && rep.getUsername() != null) { UsernameLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, rep.getUsername().toLowerCase()); if (failureModel != null) { failureModel.clearFailures(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java index 95a3c372be..88a92476ed 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/TokenIntrospectionTest.java @@ -23,6 +23,7 @@ import org.junit.Rule; import org.junit.Test; import org.keycloak.OAuth2Constants; import org.keycloak.admin.client.Keycloak; +import org.keycloak.common.util.Time; import org.keycloak.events.Event; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; @@ -31,6 +32,7 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.oidc.TokenMetadataRepresentation; import org.keycloak.services.managers.ClientManager; import org.keycloak.services.managers.RealmManager; @@ -41,6 +43,7 @@ import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebRule; +import org.keycloak.util.JsonSerialization; import org.openqa.selenium.WebDriver; import static org.junit.Assert.*; @@ -207,4 +210,90 @@ public class TokenIntrospectionTest { events.clear(); } + + @Test + public void testIntrospectAccessToken() throws Exception { + oauth.doLogin("test-user@localhost", "password"); + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + Event loginEvent = events.expectLogin().assertEvent(); + AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password"); + String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getAccessToken()); + TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class); + + assertTrue(rep.isActive()); + assertEquals("test-user@localhost", rep.getUserName()); + assertEquals("test-app", rep.getClientId()); + assertEquals(loginEvent.getUserId(), rep.getSubject()); + + events.clear(); + } + + @Test + public void testIntrospectAccessTokenSessionInvalid() throws Exception { + oauth.doLogin("test-user@localhost", "password"); + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password"); + oauth.doLogout(accessTokenResponse.getRefreshToken(), "password"); + + String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getAccessToken()); + TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class); + + assertFalse(rep.isActive()); + assertNull(rep.getUserName()); + assertNull(rep.getClientId()); + assertNull(rep.getSubject()); + + events.clear(); + } + + @Test + public void testIntrospectAccessTokenUserDisabled() throws Exception { + oauth.doLogin("test-user@localhost", "password"); + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password"); + + Event loginEvent = events.expectLogin().assertEvent(); + + UserRepresentation userRep = new UserRepresentation(); + try { + userRep.setEnabled(false); + keycloak.realm(oauth.getRealm()).users().get(loginEvent.getUserId()).update(userRep); + + String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getAccessToken()); + TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class); + + assertFalse(rep.isActive()); + assertNull(rep.getUserName()); + assertNull(rep.getClientId()); + assertNull(rep.getSubject()); + + events.clear(); + } finally { + userRep.setEnabled(true); + keycloak.realm(oauth.getRealm()).users().get(loginEvent.getUserId()).update(userRep); + } + } + + @Test + public void testIntrospectAccessTokenExpired() throws Exception { + oauth.doLogin("test-user@localhost", "password"); + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password"); + + try { + Time.setOffset(keycloak.realm(oauth.getRealm()).toRepresentation().getAccessTokenLifespan() + 1); + + String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getAccessToken()); + TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class); + + assertFalse(rep.isActive()); + assertNull(rep.getUserName()); + assertNull(rep.getClientId()); + assertNull(rep.getSubject()); + + events.clear(); + } finally { + Time.setOffset(0); + } + } }