From 562decde3570aebb9ff7f397859f99da75387ed5 Mon Sep 17 00:00:00 2001 From: rmartinc Date: Mon, 26 Feb 2024 08:48:37 +0100 Subject: [PATCH] Perform internal introspect for the access token in the account app Closes #27243 Signed-off-by: rmartinc --- .../AccessTokenIntrospectionProvider.java | 3 +- .../resources/account/AccountLoader.java | 23 ++++++-- ...ccountRestServiceLightweightTokenTest.java | 57 +++++++++++++++++++ 3 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceLightweightTokenTest.java 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 b7a43d5809..9b318112bd 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/AccessTokenIntrospectionProvider.java @@ -61,6 +61,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi this.tokenManager = new TokenManager(); } + @Override public Response introspect(String token, EventBuilder eventBuilder) { AccessToken accessToken = null; try { @@ -123,7 +124,7 @@ public class AccessTokenIntrospectionProvider implements TokenIntrospectionProvi } } - private AccessToken transformAccessToken(AccessToken token) { + public AccessToken transformAccessToken(AccessToken token) { if (token == null) { return null; } diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java b/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java index 5cce601e3d..4fe4945a83 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java @@ -17,7 +17,6 @@ package org.keycloak.services.resources.account; import org.jboss.logging.Logger; -import org.keycloak.common.Profile; import org.keycloak.http.HttpRequest; import org.keycloak.http.HttpResponse; import org.keycloak.common.enums.AccountRestApiVersion; @@ -26,6 +25,10 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.protocol.oidc.AccessTokenIntrospectionProvider; +import org.keycloak.protocol.oidc.AccessTokenIntrospectionProviderFactory; +import org.keycloak.protocol.oidc.TokenIntrospectionProvider; +import org.keycloak.representations.AccessToken; import org.keycloak.services.cors.Cors; import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.Auth; @@ -109,16 +112,26 @@ public class AccountLoader { } } - private AccountRestService getAccountRestService(ClientModel client, String versionStr) { AuthenticationManager.AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(session) - .setAudience(client.getClientId()) .authenticate(); - if (authResult == null) { throw new NotAuthorizedException("Bearer token required"); } - Auth auth = new Auth(session.getContext().getRealm(), authResult.getToken(), authResult.getUser(), client, authResult.getSession(), false); + + AccessToken accessToken = authResult.getToken(); + if (accessToken.getAudience() == null || accessToken.getResourceAccess(client.getClientId()) == null) { + // transform for introspection to get the required claims + AccessTokenIntrospectionProvider provider = (AccessTokenIntrospectionProvider) session.getProvider(TokenIntrospectionProvider.class, + AccessTokenIntrospectionProviderFactory.ACCESS_TOKEN_TYPE); + accessToken = provider.transformAccessToken(accessToken); + } + + if (!accessToken.hasAudience(client.getClientId())) { + throw new NotAuthorizedException("Invalid audience for client " + client.getClientId()); + } + + Auth auth = new Auth(session.getContext().getRealm(), accessToken, authResult.getUser(), client, authResult.getSession(), false); Cors.add(request).allowedOrigins(auth.getToken()).allowedMethods("GET", "PUT", "POST", "DELETE").auth().build(response); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceLightweightTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceLightweightTokenTest.java new file mode 100644 index 0000000000..019eac9e0d --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceLightweightTokenTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.account; + +import org.keycloak.models.Constants; +import org.keycloak.representations.idm.ClientPoliciesRepresentation; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; +import org.keycloak.representations.idm.ClientProfilesRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory; +import org.keycloak.services.clientpolicy.executor.UseLightweightAccessTokenExecutorFactory; +import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPoliciesBuilder; +import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder; +import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder; +import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder; + +/** + * + * @author rmartinc + */ +public class AccountRestServiceLightweightTokenTest extends AccountRestServiceTest { + + @Override + public void configureTestRealm(RealmRepresentation testRealm) { + super.configureTestRealm(testRealm); + + try { + // enable lightweight tokens for any client in the realm + ClientProfilesRepresentation profiles = new ClientProfilesBuilder().addProfile( + new ClientProfileBuilder().createProfile("enable lightweight tokens", "Profile Lightweight Tokens") + .addExecutor(UseLightweightAccessTokenExecutorFactory.PROVIDER_ID, null).toRepresentation()).toRepresentation(); + ClientPoliciesRepresentation policies = new ClientPoliciesBuilder().addPolicy( + new ClientPolicyBuilder().createPolicy("enable lightweight tokens", "Policy Lightweight Tokens", true) + .addCondition(AnyClientConditionFactory.PROVIDER_ID, new ClientPolicyConditionConfigurationRepresentation()) + .addProfile("enable lightweight tokens") + .toRepresentation()).toRepresentation(); + testRealm.setParsedClientProfiles(profiles); + testRealm.setParsedClientPolicies(policies); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } +}