diff --git a/authzen/services/src/main/java/org/keycloak/authorization/authzen/AuthZenResource.java b/authzen/services/src/main/java/org/keycloak/authorization/authzen/AuthZenResource.java index 1725253b803..2970a53e857 100644 --- a/authzen/services/src/main/java/org/keycloak/authorization/authzen/AuthZenResource.java +++ b/authzen/services/src/main/java/org/keycloak/authorization/authzen/AuthZenResource.java @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.regex.Pattern; import jakarta.ws.rs.BadRequestException; @@ -45,7 +46,7 @@ import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.authorization.util.Tokens; +import org.keycloak.common.constants.ServiceAccountConstants; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -53,6 +54,8 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.authorization.Permission; +import org.keycloak.services.managers.AppAuthManager; +import org.keycloak.services.managers.AuthenticationManager.AuthResult; import org.keycloak.util.JsonSerialization; public class AuthZenResource { @@ -75,10 +78,7 @@ public class AuthZenResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response evaluate(AuthZen.EvaluationRequest request) { - AccessToken token = Tokens.getAccessToken(session); - if (token == null) { - throw new NotAuthorizedException("Bearer"); - } + AccessToken token = authenticateClient(); return Response.ok(evaluateSingle(request, token)).build(); } @@ -87,10 +87,7 @@ public class AuthZenResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response evaluations(AuthZen.EvaluationsRequest request) { - AccessToken token = Tokens.getAccessToken(session); - if (token == null) { - throw new NotAuthorizedException("Bearer"); - } + AccessToken token = authenticateClient(); if (request.evaluations() == null || request.evaluations().isEmpty()) { // AuthZen 1.0 Section 7.1 @@ -133,6 +130,19 @@ public class AuthZenResource { return new AuthZen.EvaluationRequest(subject, resource, action, context); } + private AccessToken authenticateClient() { + AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(session).authenticate(); + if (authResult == null) { + throw new NotAuthorizedException("Bearer"); + } + // The evaluation of policies must only be called from a confidential client with its service account user. + // Only with that, we ensure that reveal an authorization result to the appropriate caller. + if (!authResult.client().getId().equals(authResult.user().getServiceAccountClientLink()) || !Objects.equals(authResult.session().getAuthMethod(), ServiceAccountConstants.CLIENT_AUTH)) { + throw new NotAuthorizedException("Bearer"); + } + return authResult.token(); + } + private AuthZen.EvaluationResponse evaluateSingle(AuthZen.EvaluationRequest request, AccessToken token) { if (request.subject() == null || request.resource() == null || request.action() == null) { return new AuthZen.EvaluationResponse(false); diff --git a/authzen/tests/base/src/test/java/org/keycloak/tests/authzen/AuthZenEvaluationTest.java b/authzen/tests/base/src/test/java/org/keycloak/tests/authzen/AuthZenEvaluationTest.java index 80ca6f7cb52..7284b3b0900 100644 --- a/authzen/tests/base/src/test/java/org/keycloak/tests/authzen/AuthZenEvaluationTest.java +++ b/authzen/tests/base/src/test/java/org/keycloak/tests/authzen/AuthZenEvaluationTest.java @@ -109,7 +109,7 @@ public class AuthZenEvaluationTest { // Resource properties are not used by Keycloak but are accepted and ignored per the AuthZen spec @Test public void testAdminUserAccessAdminResource() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -124,7 +124,7 @@ public class AuthZenEvaluationTest { @Test public void testRegularUserDeniedAdminResource() throws IOException { - EvaluationResult result = authzenClient("regular-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, REGULAR_USER) .action("read") @@ -138,7 +138,7 @@ public class AuthZenEvaluationTest { @Test public void testAdminUserAccessUsersResource() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -151,7 +151,7 @@ public class AuthZenEvaluationTest { @Test public void testRegularUserAccessUsersResource() throws IOException { - EvaluationResult result = authzenClient("regular-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, REGULAR_USER) .action("read") @@ -164,7 +164,7 @@ public class AuthZenEvaluationTest { @Test public void testContextPassedToClaims() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -178,7 +178,7 @@ public class AuthZenEvaluationTest { @Test public void testContextMismatchDeniesAccess() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -192,7 +192,7 @@ public class AuthZenEvaluationTest { @Test public void testMissingContextDeniesAccess() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -205,7 +205,7 @@ public class AuthZenEvaluationTest { @Test public void testNestedContextPassedToClaims() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -219,7 +219,7 @@ public class AuthZenEvaluationTest { @Test public void testNestedContextMismatchDeniesAccess() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -233,7 +233,7 @@ public class AuthZenEvaluationTest { @Test public void testUnknownResourceReturnsDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -247,7 +247,7 @@ public class AuthZenEvaluationTest { // Resource exists but the requested type does not match the resource's configured type @Test public void testResourceTypeMismatchReturnsDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -261,7 +261,7 @@ public class AuthZenEvaluationTest { // Resource has an empty type in Keycloak; an empty type string in the request should match @Test public void testEmptyTypeResourceMatchesEmptyType() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -276,7 +276,7 @@ public class AuthZenEvaluationTest { // scope has a permission granting access. Requesting the "write" action should be denied. @Test public void testActionWithoutPermissionDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("write") @@ -291,7 +291,7 @@ public class AuthZenEvaluationTest { // is authorized to access a protected resource @Test public void testClientEvaluatesUserSubjectAuthorized() throws IOException { - EvaluationResult result = authzenClient("regular-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -306,7 +306,7 @@ public class AuthZenEvaluationTest { // the required role is denied access @Test public void testClientEvaluatesUserSubjectDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, REGULAR_USER) .action("read") @@ -338,7 +338,7 @@ public class AuthZenEvaluationTest { // A CLIENT subject whose id does not match the token's client returns decision:false @Test public void testClientSubjectMismatchingTokenReturnsDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(CLIENT, "subject-id-client") .action("read") @@ -372,7 +372,7 @@ public class AuthZenEvaluationTest { // requires a "department" identity attribute matching "engineering". @Test public void testSubjectPropertiesPassedToIdentityAttributes() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .subjectProperty("department", "engineering") @@ -386,7 +386,7 @@ public class AuthZenEvaluationTest { @Test public void testSubjectPropertiesMismatchDeniesAccess() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .subjectProperty("department", "marketing") @@ -400,7 +400,7 @@ public class AuthZenEvaluationTest { @Test public void testMissingSubjectPropertiesDeniesAccess() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -422,9 +422,25 @@ public class AuthZenEvaluationTest { assertEquals(401, result.statusCode()); } + @Test + public void testPasswordGrantTokenReturnsUnauthorized() throws IOException { + AccessTokenResponse tokenResponse = oauth + .client(client.getClientId(), client.getSecret()) + .doPasswordGrantRequest("admin-user", "password"); + + EvaluationResult result = authZenClient.withAccessToken(tokenResponse.getAccessToken()) + .evaluate(AuthZenClient.evaluationRequest() + .subject(USER, ADMIN_USER) + .action("read") + .resource("endpoint", "/admin") + .build()); + + assertEquals(401, result.statusCode()); + } + @Test public void testMissingSubjectReturnsBadRequest() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .action("read") .resource("endpoint", "/admin") @@ -435,7 +451,7 @@ public class AuthZenEvaluationTest { @Test public void testMissingSubjectTypeReturnsBadRequest() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(null, ADMIN_USER) .action("read") @@ -447,7 +463,7 @@ public class AuthZenEvaluationTest { @Test public void testMissingSubjectIdReturnsBadRequest() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, null) .action("read") @@ -459,7 +475,7 @@ public class AuthZenEvaluationTest { @Test public void testUnknownUserSubjectReturnsDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "nonexistent-user") .action("read") @@ -472,7 +488,7 @@ public class AuthZenEvaluationTest { @Test public void testUnknownClientSubjectReturnsDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(CLIENT, "nonexistent-client") .action("read") @@ -488,7 +504,7 @@ public class AuthZenEvaluationTest { String url = accessEvaluationEndpoint(realmUrl()); AccessTokenResponse tokenResponse = oauth .client(client.getClientId(), client.getSecret()) - .doPasswordGrantRequest("admin-user", "password"); + .doClientCredentialsGrantAccessTokenRequest(); String json = """ {"subject":{"type":"invalid-type","id":"%s"},\ @@ -506,7 +522,7 @@ public class AuthZenEvaluationTest { @Test public void testMissingResourceReturnsBadRequest() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -517,7 +533,7 @@ public class AuthZenEvaluationTest { @Test public void testMissingResourceTypeReturnsBadRequest() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -529,7 +545,7 @@ public class AuthZenEvaluationTest { @Test public void testMissingResourceIdReturnsBadRequest() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -541,7 +557,7 @@ public class AuthZenEvaluationTest { @Test public void testMissingActionReturnsBadRequest() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .resource("endpoint", "/admin") @@ -552,7 +568,7 @@ public class AuthZenEvaluationTest { @Test public void testUndefinedActionReturnsDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("undefined-action") @@ -567,7 +583,7 @@ public class AuthZenEvaluationTest { public void testClientWithoutAuthorizationReturnsDenied() throws IOException { AccessTokenResponse tokenResponse = oauth .client(noAuthzClient.getClientId(), noAuthzClient.getSecret()) - .doPasswordGrantRequest("admin-user", "password"); + .doClientCredentialsGrantAccessTokenRequest(); EvaluationResult result = authZenClient.withAccessToken(tokenResponse.getAccessToken()) .evaluate(AuthZenClient.evaluationRequest() @@ -585,7 +601,7 @@ public class AuthZenEvaluationTest { String url = accessEvaluationEndpoint(realmUrl()); AccessTokenResponse tokenResponse = oauth .client(client.getClientId(), client.getSecret()) - .doPasswordGrantRequest("admin-user", "password"); + .doClientCredentialsGrantAccessTokenRequest(); try (SimpleHttpResponse response = simpleHttp.doPost(url) .auth(tokenResponse.getAccessToken()) @@ -599,7 +615,7 @@ public class AuthZenEvaluationTest { @Test public void testXRequestIdEchoedInResponse() throws IOException { String requestId = "test-request-id-12345"; - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate( AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) @@ -638,7 +654,7 @@ public class AuthZenEvaluationTest { String url = accessEvaluationEndpoint(realmUrl()); AccessTokenResponse tokenResponse = oauth .client(client.getClientId(), client.getSecret()) - .doPasswordGrantRequest("admin-user", "password"); + .doClientCredentialsGrantAccessTokenRequest(); try (SimpleHttpResponse response = simpleHttp.doPost(url) .auth(tokenResponse.getAccessToken()) @@ -653,7 +669,7 @@ public class AuthZenEvaluationTest { @Test public void testUsernameNamespaceResolvesUser() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "username:" + ADMIN_USER) .action("read") @@ -668,7 +684,7 @@ public class AuthZenEvaluationTest { public void testIdNamespaceResolvesUser() throws IOException { String userId = lookupUserId(ADMIN_USER); - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "id:" + userId) .action("read") @@ -681,7 +697,7 @@ public class AuthZenEvaluationTest { @Test public void testEmailNamespaceResolvesUser() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "email:admin@localhost") .action("read") @@ -694,7 +710,7 @@ public class AuthZenEvaluationTest { @Test public void testEmailNamespaceResolvesCorrectUser() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "email:regular@localhost") .action("read") @@ -709,7 +725,7 @@ public class AuthZenEvaluationTest { public void testNoNamespaceUUIDFallsBackToIdLookup() throws IOException { String userId = lookupUserId(ADMIN_USER); - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, userId) .action("read") @@ -722,7 +738,7 @@ public class AuthZenEvaluationTest { @Test public void testNoNamespaceNonUUIDFallsBackToUsernameLookup() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, ADMIN_USER) .action("read") @@ -737,7 +753,7 @@ public class AuthZenEvaluationTest { public void testEmailNamespaceWithDuplicateEmailsReturnsBadRequest() throws IOException { realm.updateWithCleanup(r -> r.duplicateEmailsAllowed(true)); - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "email:admin@localhost") .action("read") @@ -749,7 +765,7 @@ public class AuthZenEvaluationTest { @Test public void testUsernameNamespaceUnknownUserReturnsDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "username:nonexistent-user") .action("read") @@ -762,7 +778,7 @@ public class AuthZenEvaluationTest { @Test public void testIdNamespaceUnknownUserReturnsDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "id:00000000-0000-0000-0000-000000000000") .action("read") @@ -775,7 +791,7 @@ public class AuthZenEvaluationTest { @Test public void testEmailNamespaceUnknownUserReturnsDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "email:nonexistent@localhost") .action("read") @@ -788,7 +804,7 @@ public class AuthZenEvaluationTest { @Test public void testNoNamespaceUUIDUnknownUserReturnsDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "f81d4fae-7dec-11d0-a765-00a0c91e6bf6") .action("read") @@ -801,7 +817,7 @@ public class AuthZenEvaluationTest { @Test public void testEmptyIdNamespaceReturnsBadRequest() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "id:") .action("read") @@ -813,7 +829,7 @@ public class AuthZenEvaluationTest { @Test public void testEmptyUsernameNamespaceReturnsBadRequest() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "username:") .action("read") @@ -825,7 +841,7 @@ public class AuthZenEvaluationTest { @Test public void testEmptyEmailNamespaceReturnsBadRequest() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "email:") .action("read") @@ -837,7 +853,7 @@ public class AuthZenEvaluationTest { @Test public void testMixedCaseNamespaceFallsBackToUsernameLookup() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "Email:admin@localhost") .action("read") @@ -851,7 +867,7 @@ public class AuthZenEvaluationTest { @Test public void testIdNamespaceWithNonUUIDReturnsDenied() throws IOException { - EvaluationResult result = authzenClient("admin-user", "password") + EvaluationResult result = authzenClient() .evaluate(AuthZenClient.evaluationRequest() .subject(USER, "id:not-a-uuid") .action("read") @@ -872,10 +888,10 @@ public class AuthZenEvaluationTest { return keycloakUrls.getBase() + "/realms/" + realm.getName(); } - private AuthZenClient.Authenticated authzenClient(String username, String password) { + private AuthZenClient.Authenticated authzenClient() { AccessTokenResponse tokenResponse = oauth .client(client.getClientId(), client.getSecret()) - .doPasswordGrantRequest(username, password); + .doClientCredentialsGrantAccessTokenRequest(); return authZenClient.withAccessToken(tokenResponse.getAccessToken()); } @@ -1062,6 +1078,7 @@ public class AuthZenEvaluationTest { public ClientBuilder configure(ClientBuilder client) { return client .secret("secret") + .serviceAccountsEnabled() .directAccessGrantsEnabled(true); } } diff --git a/authzen/tests/base/src/test/java/org/keycloak/tests/authzen/AuthZenEvaluationsTest.java b/authzen/tests/base/src/test/java/org/keycloak/tests/authzen/AuthZenEvaluationsTest.java index 153e1d0c711..3eb0b749c4c 100644 --- a/authzen/tests/base/src/test/java/org/keycloak/tests/authzen/AuthZenEvaluationsTest.java +++ b/authzen/tests/base/src/test/java/org/keycloak/tests/authzen/AuthZenEvaluationsTest.java @@ -116,7 +116,7 @@ public class AuthZenEvaluationsTest { @Test public void testEvaluationsBatchAllGranted() throws IOException { - EvaluationsResult result = authzenClient("admin-user", "password") + EvaluationsResult result = authzenClient() .evaluations(AuthZenClient.evaluationsRequest() .subject(USER, ADMIN_USER) .action("read") @@ -136,7 +136,7 @@ public class AuthZenEvaluationsTest { @Test public void testEvaluationsBatchMixedDecisions() throws IOException { - EvaluationsResult result = authzenClient("regular-user", "password") + EvaluationsResult result = authzenClient() .evaluations(AuthZenClient.evaluationsRequest() .subject(USER, REGULAR_USER) .action("read") @@ -156,7 +156,7 @@ public class AuthZenEvaluationsTest { @Test public void testEvaluationsItemOverridesDefaults() throws IOException { - EvaluationsResult result = authzenClient("admin-user", "password") + EvaluationsResult result = authzenClient() .evaluations(AuthZenClient.evaluationsRequest() .subject(USER, ADMIN_USER) .action("read") @@ -188,6 +188,24 @@ public class AuthZenEvaluationsTest { assertEquals(401, result.statusCode()); } + @Test + public void testPasswordGrantTokenReturnsUnauthorized() throws IOException { + AccessTokenResponse tokenResponse = oauth + .client(client.getClientId(), client.getSecret()) + .doPasswordGrantRequest("admin-user", "password"); + + EvaluationsResult result = authZenClient.withAccessToken(tokenResponse.getAccessToken()) + .evaluations(AuthZenClient.evaluationsRequest() + .subject(USER, ADMIN_USER) + .action("read") + .addEvaluation(AuthZenClient.evaluationItem() + .resource("endpoint", "/admin") + .build()) + .build()); + + assertEquals(401, result.statusCode()); + } + @Test public void testEvaluationsEmptyArrayFallsBackToSingleEvaluation() throws IOException { String json = """ @@ -196,7 +214,7 @@ public class AuthZenEvaluationsTest { "action":{"name":"read"},\ "evaluations":[]}""".formatted(ADMIN_USER); - JsonNode body = postEvaluations("admin-user", "password", json); + JsonNode body = postEvaluations(json); assertTrue(body.get("decision").asBoolean()); assertNull(body.get("evaluations")); } @@ -208,16 +226,16 @@ public class AuthZenEvaluationsTest { "resource":{"type":"endpoint","id":"/admin"},\ "action":{"name":"read"}}""".formatted(ADMIN_USER); - JsonNode body = postEvaluations(ADMIN_USER, "password", json); + JsonNode body = postEvaluations(json); assertTrue(body.get("decision").asBoolean()); assertNull(body.get("evaluations")); } - private JsonNode postEvaluations(String username, String password, String json) throws IOException { + private JsonNode postEvaluations(String json) throws IOException { String url = accessEvaluationsEndpoint(keycloakUrls.getBase() + "/realms/" + realm.getName()); AccessTokenResponse tokenResponse = oauth .client(client.getClientId(), client.getSecret()) - .doPasswordGrantRequest(username, password); + .doClientCredentialsGrantAccessTokenRequest(); try (SimpleHttpResponse response = simpleHttp.doPost(url) .auth(tokenResponse.getAccessToken()) @@ -231,7 +249,7 @@ public class AuthZenEvaluationsTest { @Test public void testExecuteAllSemantic() throws IOException { - EvaluationsResult result = authzenClient("regular-user", "password") + EvaluationsResult result = authzenClient() .evaluations(AuthZenClient.evaluationsRequest() .subject(USER, REGULAR_USER) .action("read") @@ -252,7 +270,7 @@ public class AuthZenEvaluationsTest { @Test public void testDenyOnFirstDenyStopsOnDenial() throws IOException { - EvaluationsResult result = authzenClient("regular-user", "password") + EvaluationsResult result = authzenClient() .evaluations(AuthZenClient.evaluationsRequest() .subject(USER, REGULAR_USER) .action("read") @@ -272,7 +290,7 @@ public class AuthZenEvaluationsTest { @Test public void testDenyOnFirstDenyReturnsAllWhenAllPermitted() throws IOException { - EvaluationsResult result = authzenClient("admin-user", "password") + EvaluationsResult result = authzenClient() .evaluations(AuthZenClient.evaluationsRequest() .subject(USER, ADMIN_USER) .action("read") @@ -293,7 +311,7 @@ public class AuthZenEvaluationsTest { @Test public void testPermitOnFirstPermitStopsOnPermit() throws IOException { - EvaluationsResult result = authzenClient("regular-user", "password") + EvaluationsResult result = authzenClient() .evaluations(AuthZenClient.evaluationsRequest() .subject(USER, REGULAR_USER) .action("read") @@ -314,7 +332,7 @@ public class AuthZenEvaluationsTest { @Test public void testPermitOnFirstPermitStopsImmediatelyOnFirstPermit() throws IOException { - EvaluationsResult result = authzenClient("admin-user", "password") + EvaluationsResult result = authzenClient() .evaluations(AuthZenClient.evaluationsRequest() .subject(USER, ADMIN_USER) .action("read") @@ -336,7 +354,7 @@ public class AuthZenEvaluationsTest { public void testXRequestIdEchoedInResponse() throws IOException { String requestId = "test-request-id-12345"; - EvaluationsResult result = authzenClient("admin-user", "password") + EvaluationsResult result = authzenClient() .evaluations(AuthZenClient.evaluationsRequest() .subject(USER, ADMIN_USER) .action("read") @@ -377,7 +395,7 @@ public class AuthZenEvaluationsTest { String url = accessEvaluationsEndpoint(keycloakUrls.getBase() + "/realms/" + realm.getName()); AccessTokenResponse tokenResponse = oauth .client(client.getClientId(), client.getSecret()) - .doPasswordGrantRequest("admin-user", "password"); + .doClientCredentialsGrantAccessTokenRequest(); try (SimpleHttpResponse response = simpleHttp.doPost(url) .auth(tokenResponse.getAccessToken()) @@ -392,7 +410,7 @@ public class AuthZenEvaluationsTest { @Test public void testDefaultSemanticIsExecuteAll() throws IOException { - EvaluationsResult result = authzenClient("regular-user", "password") + EvaluationsResult result = authzenClient() .evaluations(AuthZenClient.evaluationsRequest() .subject(USER, REGULAR_USER) .action("read") @@ -408,10 +426,10 @@ public class AuthZenEvaluationsTest { assertEquals(2, result.evaluations().size()); } - private AuthZenClient.Authenticated authzenClient(String username, String password) { + private AuthZenClient.Authenticated authzenClient() { AccessTokenResponse tokenResponse = oauth .client(client.getClientId(), client.getSecret()) - .doPasswordGrantRequest(username, password); + .doClientCredentialsGrantAccessTokenRequest(); return authZenClient.withAccessToken(tokenResponse.getAccessToken()); } diff --git a/docs/documentation/upgrading/topics/changes/changes-26_8_0.adoc b/docs/documentation/upgrading/topics/changes/changes-26_8_0.adoc new file mode 100644 index 00000000000..9b09d8d5d54 --- /dev/null +++ b/docs/documentation/upgrading/topics/changes/changes-26_8_0.adoc @@ -0,0 +1,29 @@ +// ------------------------ Breaking changes ------------------------ // +== Breaking changes + +Breaking changes are identified as those that might require changes for existing users to their configurations or applications. +In minor or patch releases, {project_name} will only introduce breaking changes to fix bugs. + +=== + +// ------------------------ Notable changes ------------------------ // +== Notable changes + +=== AuthZEN changes + +When calling {project_name}'s experimental AuthZEN endpoints, the caller now needs to be an authenticated confidential client's access token. Previously, it could be called with a user's access token. + +// ------------------------ Deprecated features ------------------------ // +== Deprecated features + +The following sections provide details on deprecated features. + +=== + +// ------------------------ Removed features ------------------------ // +== Removed features + +The following features have been removed from this release. + +=== +