diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java index 81d556ed1d..17002ab82e 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java +++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java @@ -23,6 +23,7 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.policy.evaluation.Result; import org.keycloak.authorization.util.Permissions; +import org.keycloak.models.ClientModel; import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.authorization.DecisionEffect; import org.keycloak.representations.idm.authorization.PolicyEvaluationResponse; @@ -55,6 +56,12 @@ public class PolicyEvaluationResponseBuilder { authorizationData.setPermissions(Permissions.permits(results, null, authorization, resourceServer)); accessToken.setAuthorization(authorizationData); + ClientModel clientModel = authorization.getRealm().getClientById(resourceServer.getId()); + + if (!accessToken.hasAudience(clientModel.getClientId())) { + accessToken.audience(clientModel.getClientId()); + } + response.setRpt(accessToken); if (results.stream().anyMatch(evaluationResult -> evaluationResult.getEffect().equals(Decision.Effect.DENY))) { diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java index d407613d6d..3f9fb06c14 100644 --- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java +++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java @@ -25,7 +25,6 @@ import org.keycloak.authorization.authorization.representation.AuthorizationRequ import org.keycloak.authorization.authorization.representation.AuthorizationResponse; import org.keycloak.authorization.common.KeycloakEvaluationContext; import org.keycloak.authorization.common.KeycloakIdentity; -import org.keycloak.authorization.entitlement.representation.EntitlementResponse; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; @@ -39,6 +38,7 @@ import org.keycloak.authorization.util.Permissions; import org.keycloak.authorization.util.Tokens; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInputException; +import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.protocol.oidc.TokenManager; @@ -119,7 +119,7 @@ public class AuthorizationTokenService { List entitlements = Permissions.permits(result, authorizationRequest.getMetadata(), authorization, resourceServer); if (!entitlements.isEmpty()) { - AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(entitlements, identity.getAccessToken())); + AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(entitlements, identity.getAccessToken(), resourceServer)); return Cors.add(httpRequest, Response.status(Status.CREATED).entity(response)).allowedOrigins(identity.getAccessToken()) .allowedMethods("POST") .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build(); @@ -254,12 +254,18 @@ public class AuthorizationTokenService { return this.authorization.getKeycloakSession().getContext().getRealm(); } - private String createRequestingPartyToken(List permissions, AccessToken accessToken) { + private String createRequestingPartyToken(List permissions, AccessToken accessToken, ResourceServer resourceServer) { AccessToken.Authorization authorization = new AccessToken.Authorization(); authorization.setPermissions(permissions); accessToken.setAuthorization(authorization); + ClientModel clientModel = this.authorization.getRealm().getClientById(resourceServer.getId()); + + if (!accessToken.hasAudience(clientModel.getClientId())) { + accessToken.audience(clientModel.getClientId()); + } + return new TokenManager().encodeToken(session, getRealm(), accessToken); } diff --git a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java index 0108eab934..b7a327fbbe 100644 --- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java +++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java @@ -167,7 +167,7 @@ public class EntitlementService { List entitlements = Permissions.permits(result, metadata, authorization, resourceServer); if (!entitlements.isEmpty()) { - return Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements, identity.getAccessToken())))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build(); + return Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements, identity.getAccessToken(), resourceServer)))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build(); } } catch (Exception cause) { logger.error(cause); @@ -184,13 +184,19 @@ public class EntitlementService { .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build(); } - private String createRequestingPartyToken(List permissions, AccessToken accessToken) { + private String createRequestingPartyToken(List permissions, AccessToken accessToken, ResourceServer resourceServer) { RealmModel realm = this.authorization.getKeycloakSession().getContext().getRealm(); AccessToken.Authorization authorization = new AccessToken.Authorization(); authorization.setPermissions(permissions); accessToken.setAuthorization(authorization); + ClientModel clientModel = realm.getClientById(resourceServer.getId()); + + if (!accessToken.hasAudience(clientModel.getClientId())) { + accessToken.audience(clientModel.getClientId()); + } + return new TokenManager().encodeToken(this.authorization.getKeycloakSession(), realm, accessToken); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractAuthzTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractAuthzTest.java index 00917cd6d7..02d18653de 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractAuthzTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractAuthzTest.java @@ -1,6 +1,10 @@ package org.keycloak.testsuite.authz; import org.junit.BeforeClass; +import org.keycloak.authorization.client.representation.EntitlementResponse; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.JWSInputException; +import org.keycloak.representations.AccessToken; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.ProfileAssume; @@ -13,4 +17,15 @@ public abstract class AbstractAuthzTest extends AbstractKeycloakTest { public static void enabled() { ProfileAssume.assumePreview(); } + + protected AccessToken toAccessToken(String rpt) { + AccessToken accessToken; + + try { + accessToken = new JWSInput(rpt).readJsonContent(AccessToken.class); + } catch (JWSInputException cause) { + throw new RuntimeException("Failed to deserialize RPT", cause); + } + return accessToken; + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RequireUmaAuthorizationScopeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthorizationAPITest.java similarity index 84% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RequireUmaAuthorizationScopeTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthorizationAPITest.java index 0b24c2aeaf..5529cfd442 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RequireUmaAuthorizationScopeTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthorizationAPITest.java @@ -39,13 +39,14 @@ import org.keycloak.authorization.client.representation.AuthorizationRequest; import org.keycloak.authorization.client.representation.AuthorizationResponse; import org.keycloak.authorization.client.representation.PermissionRequest; import org.keycloak.authorization.client.util.HttpResponseException; +import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.authorization.JSPolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; -import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.util.ClientBuilder; +import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RoleBuilder; import org.keycloak.testsuite.util.RolesBuilder; @@ -55,7 +56,7 @@ import org.keycloak.util.JsonSerialization; /** * @author Pedro Igor */ -public class RequireUmaAuthorizationScopeTest extends AbstractAuthzTest { +public class AuthorizationAPITest extends AbstractAuthzTest { @Override public void addTestRealms(List testRealms) { @@ -69,6 +70,11 @@ public class RequireUmaAuthorizationScopeTest extends AbstractAuthzTest { .redirectUris("http://localhost/resource-server-test") .defaultRoles("uma_protection") .directAccessGrants()) + .client(ClientBuilder.create().clientId("test-client") + .secret("secret") + .authorizationServicesEnabled(true) + .redirectUris("http://localhost/test-client") + .directAccessGrants()) .build()); } @@ -143,6 +149,22 @@ public class RequireUmaAuthorizationScopeTest extends AbstractAuthzTest { failAccessTokenWithoutUmaAuthorization(); } + @Test + public void testResourceServerAsAudience() throws Exception { + AuthzClient authzClient = getAuthzClient(); + PermissionRequest request = new PermissionRequest(); + + request.setResourceSetName("Resource A"); + + String accessToken = new OAuthClient().realm("authz-test").clientId("test-client").doGrantAccessTokenRequest("secret", "marta", "password").getAccessToken(); + String ticket = authzClient.protection().permission().forResource(request).getTicket(); + AuthorizationResponse response = authzClient.authorization(accessToken).authorize(new AuthorizationRequest(ticket)); + + assertNotNull(response.getRpt()); + AccessToken rpt = toAccessToken(response.getRpt()); + assertEquals("resource-server-test", rpt.getAudience()[0]); + } + private RealmResource getRealm() throws Exception { return adminClient.realm("authz-test"); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java index 2145c6f82e..c0a8868c2b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java @@ -41,6 +41,7 @@ import org.keycloak.authorization.client.representation.EntitlementResponse; import org.keycloak.authorization.client.representation.PermissionRequest; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInputException; +import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.authorization.JSPolicyRepresentation; @@ -49,6 +50,7 @@ import org.keycloak.representations.idm.authorization.ResourcePermissionRepresen import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.util.ClientBuilder; +import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RoleBuilder; import org.keycloak.testsuite.util.RolesBuilder; @@ -74,6 +76,11 @@ public class EntitlementAPITest extends AbstractAuthzTest { .redirectUris("http://localhost/resource-server-test") .defaultRoles("uma_protection") .directAccessGrants()) + .client(ClientBuilder.create().clientId("test-client") + .secret("secret") + .authorizationServicesEnabled(true) + .redirectUris("http://localhost/test-client") + .directAccessGrants()) .build()); } @@ -155,7 +162,7 @@ public class EntitlementAPITest extends AbstractAuthzTest { request.setMetadata(metadata); EntitlementResponse response = getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request); - AccessToken rpt = toAccessToken(response); + AccessToken rpt = toAccessToken(response.getRpt()); List permissions = rpt.getAuthorization().getPermissions(); @@ -175,7 +182,7 @@ public class EntitlementAPITest extends AbstractAuthzTest { request.setRpt(response.getRpt()); response = getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request); - rpt = toAccessToken(response); + rpt = toAccessToken(response.getRpt()); permissions = rpt.getAuthorization().getPermissions(); @@ -199,7 +206,7 @@ public class EntitlementAPITest extends AbstractAuthzTest { request.setRpt(response.getRpt()); response = getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request); - rpt = toAccessToken(response); + rpt = toAccessToken(response.getRpt()); permissions = rpt.getAuthorization().getPermissions(); @@ -222,7 +229,7 @@ public class EntitlementAPITest extends AbstractAuthzTest { request.setRpt(response.getRpt()); response = getAuthzClient().entitlement(authzClient.obtainAccessToken("marta", "password").getToken()).get("resource-server-test", request); - rpt = toAccessToken(response); + rpt = toAccessToken(response.getRpt()); permissions = rpt.getAuthorization().getPermissions(); @@ -234,8 +241,21 @@ public class EntitlementAPITest extends AbstractAuthzTest { assertEquals("Resource 12", permissions.get(4).getResourceSetName()); } + @Test + public void testResourceServerAsAudience() throws Exception { + EntitlementRequest request = new EntitlementRequest(); + + request.addPermission(new PermissionRequest("Resource 1")); + + String accessToken = new OAuthClient().realm("authz-test").clientId("test-client").doGrantAccessTokenRequest("secret", "marta", "password").getAccessToken(); + EntitlementResponse response = getAuthzClient().entitlement(accessToken).get("resource-server-test", request); + AccessToken rpt = toAccessToken(response.getRpt()); + + assertEquals("resource-server-test", rpt.getAudience()[0]); + } + private void assertResponse(AuthorizationRequestMetadata metadata, Supplier responseSupplier) { - AccessToken.Authorization authorization = toAccessToken(responseSupplier.get()).getAuthorization(); + AccessToken.Authorization authorization = toAccessToken(responseSupplier.get().getRpt()).getAuthorization(); List permissions = authorization.getPermissions(); @@ -251,17 +271,6 @@ public class EntitlementAPITest extends AbstractAuthzTest { } } - private AccessToken toAccessToken(EntitlementResponse response) { - AccessToken accessToken; - - try { - accessToken = new JWSInput(response.getRpt()).readJsonContent(AccessToken.class); - } catch (JWSInputException cause) { - throw new RuntimeException("Failed to deserialize RPT", cause); - } - return accessToken; - } - private RealmResource getRealm() throws Exception { return adminClient.realm("authz-test"); }