From 1ccce63aa48ba1e60eb339330232ea37ad0bc42d Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Thu, 7 May 2026 02:32:43 -0300 Subject: [PATCH] Resolve SA before resolving users from username or email Closes #48592 Signed-off-by: Pedro Igor --- .../policy/evaluation/DefaultEvaluation.java | 13 ++++-- .../testsuite/authz/PolicyEvaluationTest.java | 42 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java index 9d05a49d21b..a7207c10cec 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java @@ -189,15 +189,22 @@ public class DefaultEvaluation implements Evaluation { } RealmModel realm = session.getContext().getRealm(); user = session.users().getUserById(realm, id); + + if (Objects.isNull(user)) { + // in case the id references a service account + ClientModel client = realm.getClientById(id); + + if (client != null) { + user = session.users().getServiceAccount(client); + } + } + if (Objects.isNull(user)) { user = session.users().getUserByUsername(realm, id); } if (Objects.isNull(user)) { user = session.users().getUserByEmail(realm, id); } - if (Objects.isNull(user)) { - user = session.users().getServiceAccount(realm.getClientById(id)); - } } cache.put(id, user); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java index 58494aa9f1a..cb9ecac75bf 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java @@ -50,6 +50,7 @@ import org.keycloak.common.Profile.Feature; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocol; @@ -674,6 +675,47 @@ public class PolicyEvaluationTest extends AbstractAuthzTest { }; } + @Test + public void testResolveServiceAccountByClientId() { + testingClient.server().run(PolicyEvaluationTest::testResolveServiceAccountByClientId); + } + + public static void testResolveServiceAccountByClientId(KeycloakSession session) { + RealmModel realm = session.realms().getRealmByName("authz-test"); + session.getContext().setRealm(realm); + AuthorizationProvider authorization = session.getProvider(AuthorizationProvider.class); + ClientModel clientModel = session.clients().getClientByClientId(realm, "resource-server-test"); + StoreFactory storeFactory = authorization.getStoreFactory(); + ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(clientModel); + + // get the service account user and assign a realm role + UserModel serviceAccount = session.users().getServiceAccount(clientModel); + assertNotNull(serviceAccount); + RoleModel roleA = realm.getRole("role-a"); + assertNotNull(roleA); + serviceAccount.grantRole(roleA); + + // create a simple user policy (type doesn't matter, we test through the Realm interface) + UserPolicyRepresentation policyRepresentation = new UserPolicyRepresentation(); + policyRepresentation.setName("testResolveServiceAccountByClientId"); + policyRepresentation.addUser(serviceAccount.getId()); + Policy policy = storeFactory.getPolicyStore().create(resourceServer, policyRepresentation); + + DefaultEvaluation evaluation = createEvaluation(session, authorization, resourceServer, policy); + + // resolve service account using the client's internal ID (the fix scenario) + Assertions.assertTrue(evaluation.getRealm().isUserInRealmRole(clientModel.getId(), "role-a"), + "Service account should be resolved by client internal ID"); + + // resolve service account using the service account username (regression check) + Assertions.assertTrue(evaluation.getRealm().isUserInRealmRole(serviceAccount.getUsername(), "role-a"), + "Service account should still be resolved by username"); + + // non-existent ID should not cause errors (NPE check) + Assertions.assertFalse(evaluation.getRealm().isUserInRealmRole("non-existent-id", "role-a"), + "Non-existent ID should return false without errors"); + } + @Test public void testEvaluation() { RealmResource realmApi = realmsResouce().realm("authz-test");