diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java index 9168aa01af..62c418c6dc 100644 --- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java +++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java @@ -35,6 +35,7 @@ import org.keycloak.representations.AccessToken; import org.keycloak.representations.IDToken; import org.keycloak.saml.common.util.StringUtil; import org.keycloak.services.ErrorResponseException; +import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.util.DefaultClientSessionContext; import org.keycloak.util.JsonSerialization; @@ -136,6 +137,10 @@ public class KeycloakIdentity implements Identity { throw new RuntimeException("No active session associated with the token"); } + if (AuthenticationManager.isSessionValid(realm, userSession) && token.isIssuedBeforeSessionStart(userSession.getStarted())) { + throw new RuntimeException("Invalid token"); + } + ClientModel client = realm.getClientByClientId(token.getIssuedFor()); AuthenticatedClientSessionModel clientSessionModel = userSession.getAuthenticatedClientSessionByClient(client.getId()); @@ -307,7 +312,9 @@ public class KeycloakIdentity implements Identity { if (userSession == null) { userSession = sessions.getOfflineUserSession(realm, accessToken.getSessionState()); } - + if (AuthenticationManager.isSessionValid(realm, userSession) && accessToken.isIssuedBeforeSessionStart(userSession.getStarted())) { + return null; + } return userSession.getUser(); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java index 959c7b73f7..e2e954cfd0 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java @@ -29,6 +29,8 @@ import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.policy.evaluation.EvaluationContext; import org.keycloak.common.Profile; +import org.keycloak.events.EventBuilder; +import org.keycloak.events.EventType; import org.keycloak.models.AdminRoles; import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; @@ -52,6 +54,7 @@ import java.util.Map; import jakarta.ws.rs.ForbiddenException; import org.keycloak.services.util.DefaultClientSessionContext; +import org.keycloak.services.util.UserSessionUtil; import org.keycloak.utils.RoleResolveUtil; /** @@ -107,20 +110,17 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage private void initIdentity(KeycloakSession session, AdminAuth auth) { final String issuedFor = auth.getToken().getIssuedFor(); AccessToken accessToken = auth.getToken(); - //support for lightweight access token - if (auth.getToken().getSubject() == null) { + ClientModel client = adminsRealm.getClientByClientId(issuedFor); + //support for lightweight access token and transient session + if (accessToken.getSubject() == null || (accessToken.getSessionId() == null && accessToken.getResourceAccess().isEmpty() && accessToken.getRealmAccess() == null)) { //get user session - UserSessionProvider sessions = session.sessions(); - UserSessionModel userSession = sessions.getUserSession(adminsRealm, auth.getToken().getSessionId()); - if (userSession == null) { - userSession = sessions.getOfflineUserSession(adminsRealm, auth.getToken().getSessionId()); - } + EventBuilder event = new EventBuilder(adminsRealm, session); + event.event(EventType.INTROSPECT_TOKEN); + UserSessionModel userSession = UserSessionUtil.findValidSession(session, adminsRealm, accessToken, event, client); if (userSession != null) { //get client session - ClientModel client = adminsRealm.getClientByClientId(issuedFor); AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId()); - //set realm roles ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, auth.getToken().getScope(), session); AccessToken.Access realmAccess = RoleResolveUtil.getResolvedRealmRoles(session, clientSessionCtx, false); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index 8802642024..56944fe1b5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java @@ -700,12 +700,9 @@ public abstract class AbstractKeycloakTest { Time.setOffset(offset); Map result = testingClient.testing().setTimeOffset(Collections.singletonMap("offset", String.valueOf(offset))); - // force refreshing token after time offset has changed - try { - adminClient.tokenManager().refreshToken(); - } catch (RuntimeException e) { - adminClient.tokenManager().grantToken(); - } + // force getting new token after time offset has changed + adminClient.tokenManager().grantToken(); + return String.valueOf(result); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java index 5d1f56ed9b..69ba7af2bb 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java @@ -32,6 +32,7 @@ import org.keycloak.admin.client.resource.ClientScopeResource; import org.keycloak.admin.client.resource.ProtocolMappersResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.common.Profile; +import org.keycloak.models.AdminRoles; import org.keycloak.models.Constants; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.utils.ModelToRepresentation; @@ -68,6 +69,7 @@ import org.keycloak.utils.MediaType; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -521,6 +523,41 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest { } } + @Test + public void testAdminApiWithLightweightAccessTokenAndTransientSession() { + RealmResource masterRealm = realmsResouce().realm("master"); + ClientRepresentation transientClient = KeycloakModelUtils.createClient(realmsResouce().realm("master").toRepresentation(), "transient_client"); + transientClient.setServiceAccountsEnabled(Boolean.TRUE); + transientClient.setAttributes(new HashMap<>()); + transientClient.getAttributes().put(Constants.USE_LIGHTWEIGHT_ACCESS_TOKEN_ENABLED, String.valueOf(true)); + masterRealm.clients().create(transientClient); + transientClient = masterRealm.clients().findByClientId(transientClient.getClientId()).get(0); + + UserRepresentation userRep = masterRealm.clients().get(transientClient.getId()).getServiceAccountUser(); + masterRealm.users().get(userRep.getId()).roles().realmLevel().add(Collections.singletonList(masterRealm.roles().get(AdminRoles.ADMIN).toRepresentation())); + try { + oauth.realm("master"); + oauth.clientId(transientClient.getClientId()); + OAuthClient.AccessTokenResponse tokenResponse = oauth.doClientCredentialsGrantAccessTokenRequest(transientClient.getSecret()); + String accessTokenString = tokenResponse.getAccessToken(); + Assert.assertNull(tokenResponse.getRefreshToken()); + AccessToken accessToken = oauth.verifyToken(accessTokenString); + Assert.assertNotNull(accessToken.getSubject()); + Assert.assertNull(accessToken.getSessionId()); + + CloseableHttpClient client = HttpClientBuilder.create().build(); + HttpGet get = new HttpGet(OAuthClient.SERVER_ROOT + "/auth/admin/realms/master"); + get.setHeader("Authorization", "Bearer " + accessTokenString); + CloseableHttpResponse response = client.execute(get); + Assert.assertEquals(200, response.getStatusLine().getStatusCode()); + RealmRepresentation realmRepresentation = JsonSerialization.readValue(response.getEntity().getContent(), RealmRepresentation.class); + Assert.assertEquals("master", realmRepresentation.getRealm()); + + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + } + private void removeSession(final String sessionId) { testingClient.testing().removeExpired(REALM_NAME); try {