From 568fba03e2d57292c0a19ca2cff827073de00fe6 Mon Sep 17 00:00:00 2001 From: vramik Date: Mon, 19 Oct 2026 19:42:52 +0200 Subject: [PATCH] Service account user ID available on client representation instead of exposing separate endpoint returning whole user representation Closes #54047 Signed-off-by: vramik --- .../idm/ClientRepresentation.java | 9 +++ .../release_notes/topics/27_0_0.adoc | 9 +++ .../topics/changes/changes-27_0_0.adoc | 22 ++++++ .../admin/client/resource/ClientResource.java | 6 -- .../service-account/ServiceAccount.tsx | 27 ++++---- .../src/defs/clientRepresentation.ts | 1 + .../src/resources/clients.ts | 13 ---- .../test/clients.spec.ts | 11 ++- .../admin/cli/v2/KcAdmV2KeystoreCLITest.java | 6 +- .../scim/tck/AdminGroupProtectionTest.java | 24 +++---- .../tests/scim/tck/AuthorizationTest.java | 52 +++++++------- .../keycloak/tests/scim/tck/GroupTest.java | 11 ++- .../org/keycloak/tests/scim/tck/UserTest.java | 19 +++--- .../scim/client/ScimClientSupplier.java | 5 +- .../models/utils/ModelToRepresentation.java | 7 ++ .../partialimport/PartialImportManager.java | 21 ++++++ .../resources/admin/ClientResource.java | 26 ++----- .../resources/admin/ClientsResource.java | 3 + .../SsfTransmitterEventEmitterTests.java | 5 +- .../org/keycloak/tests/admin/ClientTest.java | 3 +- .../tests/admin/ImpersonationTest.java | 6 +- .../keycloak/tests/admin/PermissionsTest.java | 1 - .../client/ServiceAccountClientTest.java | 67 +++++++++++++++++-- .../PartialImportClientTest.java | 2 +- .../tests/client/ClientRegistrationTest.java | 11 ++- .../OIDCClientRegistrationCorsTest.java | 2 +- .../client/SAMLClientRegistrationTest.java | 2 +- .../ClientAgentDelegationTest.java | 2 +- .../ClientDelegationCibaTest.java | 2 +- .../tokenexchange/ClientDelegationTest.java | 4 +- .../StandardBaseTokenExchangeV2Test.java | 3 +- .../OID4VCredentialOfferPreAuthTest.java | 3 +- .../OrganizationOIDCProtocolMapperTest.java | 2 +- .../testsuite/authz/GroupNamePolicyTest.java | 4 +- .../testsuite/authz/RolePolicyTest.java | 4 +- .../authz/admin/AuthorizationTest.java | 12 ++-- .../StandardTokenExchangeV2Test.java | 3 +- .../oidc/LightWeightAccessTokenTest.java | 8 +-- .../testsuite/util/ClientManager.java | 3 +- 39 files changed, 258 insertions(+), 163 deletions(-) diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java index f8957a354712..17bccac2592e 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java @@ -52,6 +52,7 @@ public class ClientRepresentation { protected Boolean implicitFlowEnabled; protected Boolean directAccessGrantsEnabled; protected Boolean serviceAccountsEnabled; + protected String serviceAccountUserId; protected Boolean authorizationServicesEnabled; @Deprecated protected Boolean directGrantsOnly; @@ -276,6 +277,14 @@ public void setServiceAccountsEnabled(Boolean serviceAccountsEnabled) { this.serviceAccountsEnabled = serviceAccountsEnabled; } + public String getServiceAccountUserId() { + return serviceAccountUserId; + } + + public void setServiceAccountUserId(String serviceAccountUserId) { + this.serviceAccountUserId = serviceAccountUserId; + } + public Boolean getAuthorizationServicesEnabled() { if (authorizationSettings != null) { return true; diff --git a/docs/documentation/release_notes/topics/27_0_0.adoc b/docs/documentation/release_notes/topics/27_0_0.adoc index 17e94f3f9ef0..c13b576cb495 100644 --- a/docs/documentation/release_notes/topics/27_0_0.adoc +++ b/docs/documentation/release_notes/topics/27_0_0.adoc @@ -1,3 +1,12 @@ += Administration + +== Service account user ID available on client representation + +The service account user ID is now included directly in the client representation as the `serviceAccountUserId` field. +This removes the need for a separate API call to discover the service account user, simplifying client administration and role mapping workflows. +The dedicated `GET /admin/realms/\{realm}/clients/\{id}/service-account-user` endpoint has been removed. +See the upgrading guide for migration details. + = Security and Standards == Credential ID in credential update events diff --git a/docs/documentation/upgrading/topics/changes/changes-27_0_0.adoc b/docs/documentation/upgrading/topics/changes/changes-27_0_0.adoc index 5b3606a54982..024fa418c441 100644 --- a/docs/documentation/upgrading/topics/changes/changes-27_0_0.adoc +++ b/docs/documentation/upgrading/topics/changes/changes-27_0_0.adoc @@ -141,6 +141,28 @@ Providers extending `OAuth2IdentityProviderConfig` can instead override `getClie The Client Initiated Backchannel Authentication (CIBA) grant is now rejected for public clients, as required by the https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html#rfc.section.7.1[CIBA specification], which mandates client authentication on both the backchannel authentication endpoint and the token endpoint. Previously, a public client with the CIBA grant enabled could complete the flow without authenticating and obtain tokens for the approving user. +=== Service account user endpoint removed from Admin REST API +// https://github.com/keycloak/keycloak/issues/54047 + +The `GET /admin/realms/\{realm}/clients/\{id}/service-account-user` endpoint has been removed. +The service account user ID is now available directly on the client representation as the `serviceAccountUserId` field, which is populated when `serviceAccountsEnabled` is `true`. +This field is read-only and managed by the server. + +If you previously called this endpoint to obtain the service account user ID for role mapping or other operations, retrieve the client representation instead: + +[source,java] +---- +// Before: +UserRepresentation sa = clientResource.getServiceAccountUser(); +String saUserId = sa.getId(); + +// After: +String saUserId = clientResource.toRepresentation().getServiceAccountUserId(); +---- + +The `getServiceAccountUser()` method has also been removed from the Java and JavaScript admin client libraries. +If you need the full user representation for the service account, use the user ID from the client representation to fetch it via the Users API. + // ------------------------ Notable changes ------------------------ // == Notable changes diff --git a/integration/admin-client-core/src/main/java/org/keycloak/admin/client/resource/ClientResource.java b/integration/admin-client-core/src/main/java/org/keycloak/admin/client/resource/ClientResource.java index 2447dfe97ff9..ebc387b68413 100644 --- a/integration/admin-client-core/src/main/java/org/keycloak/admin/client/resource/ClientResource.java +++ b/integration/admin-client-core/src/main/java/org/keycloak/admin/client/resource/ClientResource.java @@ -38,7 +38,6 @@ import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.ManagementPermissionReference; import org.keycloak.representations.idm.ManagementPermissionRepresentation; -import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserSessionRepresentation; /** @@ -204,11 +203,6 @@ public interface ClientResource { @Path("optional-client-scopes/{clientScopeId}") void removeOptionalClientScope(@PathParam("clientScopeId") String clientScopeId); - @Path("/service-account-user") - @GET - @Produces(MediaType.APPLICATION_JSON) - UserRepresentation getServiceAccountUser(); - @Path("nodes") @POST @Consumes(MediaType.APPLICATION_JSON) diff --git a/js/apps/admin-ui/src/clients/service-account/ServiceAccount.tsx b/js/apps/admin-ui/src/clients/service-account/ServiceAccount.tsx index 3135992d08f4..37b68db3ae69 100644 --- a/js/apps/admin-ui/src/clients/service-account/ServiceAccount.tsx +++ b/js/apps/admin-ui/src/clients/service-account/ServiceAccount.tsx @@ -1,6 +1,5 @@ import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation"; import type { RoleMappingPayload } from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation"; -import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation"; import { useAlerts, useFetch } from "@keycloak/keycloak-ui-shared"; import { AlertVariant, PageSection } from "@patternfly/react-core"; import { InfoCircleIcon } from "@patternfly/react-icons"; @@ -27,20 +26,24 @@ export const ServiceAccount = ({ client }: ServiceAccountProps) => { const { addAlert, addError } = useAlerts(); const { realm } = useRealm(); - const [serviceAccount, setServiceAccount] = useState(); + const [serviceAccountUserId, setServiceAccountUserId] = useState(); const { hasAccess } = useAccess(); const hasManageClients = hasAccess("manage-clients"); useFetch( - () => - adminClient.clients.getServiceAccountUser({ + async () => { + const freshClient = await adminClient.clients.findOne({ id: client.id!, - }), - (serviceAccount) => setServiceAccount(serviceAccount), + }); + return freshClient?.serviceAccountUserId; + }, + (id) => setServiceAccountUserId(id), [], ); + const serviceAccountUsername = `service-account-${client.clientId}`; + const assignRoles = async (rows: Row[]) => { try { const realmRoles = rows @@ -48,7 +51,7 @@ export const ServiceAccount = ({ client }: ServiceAccountProps) => { .map((row) => row.role as RoleMappingPayload) .flat(); await adminClient.users.addRealmRoleMappings({ - id: serviceAccount?.id!, + id: serviceAccountUserId!, roles: realmRoles, }); await Promise.all( @@ -56,7 +59,7 @@ export const ServiceAccount = ({ client }: ServiceAccountProps) => { .filter((row) => row.client !== undefined) .map((row) => adminClient.users.addClientRoleMappings({ - id: serviceAccount?.id!, + id: serviceAccountUserId!, clientUniqueId: row.client!.id!, roles: [row.role as RoleMappingPayload], }), @@ -67,7 +70,7 @@ export const ServiceAccount = ({ client }: ServiceAccountProps) => { addError("roleMappingUpdatedError", error); } }; - return serviceAccount ? ( + return serviceAccountUserId ? ( <> @@ -75,16 +78,16 @@ export const ServiceAccount = ({ client }: ServiceAccountProps) => { {""} - {{ link: serviceAccount.username }} + {{ link: serviceAccountUsername }} { urlParamKeys: ["id", "roleName"], }); - /** - * Service account user - */ - - public getServiceAccountUser = this.makeRequest< - { id: string }, - UserRepresentation - >({ - method: "GET", - path: "/{id}/service-account-user", - urlParamKeys: ["id"], - }); - /** * Client secret */ diff --git a/js/libs/keycloak-admin-client/test/clients.spec.ts b/js/libs/keycloak-admin-client/test/clients.spec.ts index 475efa6a18b5..b2f5cd02e053 100644 --- a/js/libs/keycloak-admin-client/test/clients.spec.ts +++ b/js/libs/keycloak-admin-client/test/clients.spec.ts @@ -257,13 +257,12 @@ describe("Clients", () => { expect(typeof installationProvider).to.be.equal("string"); }); - it("get service account user", async () => { - const serviceAccountUser = - await kcAdminClient.clients.getServiceAccountUser({ - id: currentClient.id!, - }); + it("get service account user id", async () => { + const client = await kcAdminClient.clients.findOne({ + id: currentClient.id!, + }); - expect(serviceAccountUser).to.be.ok; + expect(client?.serviceAccountUserId).to.be.ok; }); }); diff --git a/rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/cli/v2/KcAdmV2KeystoreCLITest.java b/rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/cli/v2/KcAdmV2KeystoreCLITest.java index b74b2eea6747..c96434947f1f 100644 --- a/rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/cli/v2/KcAdmV2KeystoreCLITest.java +++ b/rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/cli/v2/KcAdmV2KeystoreCLITest.java @@ -147,10 +147,10 @@ private void setupJwtClient() { .substring(response.getLocation().getPath().lastIndexOf('/') + 1); response.close(); - var serviceAccountUser = adminClient.realm("master").clients() - .get(clientUuid).getServiceAccountUser(); + var serviceAccountUserId = adminClient.realm("master").clients() + .get(clientUuid).toRepresentation().getServiceAccountUserId(); var adminRole = adminClient.realm("master").roles().get("admin").toRepresentation(); - adminClient.realm("master").users().get(serviceAccountUser.getId()) + adminClient.realm("master").users().get(serviceAccountUserId) .roles().realmLevel().add(java.util.List.of(adminRole)); try { diff --git a/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/AdminGroupProtectionTest.java b/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/AdminGroupProtectionTest.java index 7c6e421b3728..2a23d10b90cb 100644 --- a/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/AdminGroupProtectionTest.java +++ b/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/AdminGroupProtectionTest.java @@ -379,13 +379,13 @@ public void testServiceAccountNotExposedInGroupMembers() { realm.cleanup().add(r -> r.clients().get(clientDbId).remove()); String regularGroupId = getGroupId(REGULAR_GROUP); - UserRepresentation serviceAccount = realm.admin().clients().get(clientDbId).getServiceAccountUser(); - realm.admin().users().get(serviceAccount.getId()).joinGroup(regularGroupId); + String serviceAccountUserId = realm.admin().clients().get(clientDbId).toRepresentation().getServiceAccountUserId(); + realm.admin().users().get(serviceAccountUserId).joinGroup(regularGroupId); Group group = client.groups().get(regularGroupId, List.of("members")); List members = group.getMembers(); if (members != null) { - boolean exposed = members.stream().anyMatch(m -> serviceAccount.getId().equals(m.getValue())); + boolean exposed = members.stream().anyMatch(m -> serviceAccountUserId.equals(m.getValue())); assertFalse(exposed, "Service account must not appear in SCIM group members"); } } @@ -405,11 +405,11 @@ public void testCannotAddServiceAccountToGroupViaGroupPatch() { realm.cleanup().add(r -> r.clients().get(clientDbId).remove()); String regularGroupId = getGroupId(REGULAR_GROUP); - UserRepresentation serviceAccount = realm.admin().clients().get(clientDbId).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(clientDbId).toRepresentation().getServiceAccountUserId(); try { client.groups().patch(regularGroupId, PatchRequest.create() - .add("members", serviceAccount.getId()) + .add("members", serviceAccountUserId) .build()); fail("Should not be able to add a service account to a group via SCIM PATCH"); } catch (ScimClientException sce) { @@ -432,14 +432,14 @@ public void testCannotRemoveServiceAccountFromGroupViaGroupPatch() { realm.cleanup().add(r -> r.clients().get(clientDbId).remove()); String regularGroupId = getGroupId(REGULAR_GROUP); - UserRepresentation serviceAccount = realm.admin().clients().get(clientDbId).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(clientDbId).toRepresentation().getServiceAccountUserId(); // Add service account to group via Admin API (bypassing SCIM protection) - realm.admin().users().get(serviceAccount.getId()).joinGroup(regularGroupId); + realm.admin().users().get(serviceAccountUserId).joinGroup(regularGroupId); try { client.groups().patch(regularGroupId, PatchRequest.create() - .remove("members[value eq \"" + serviceAccount.getId() + "\"]") + .remove("members[value eq \"" + serviceAccountUserId + "\"]") .build()); fail("Should not be able to remove a service account from a group via SCIM PATCH"); } catch (ScimClientException sce) { @@ -447,7 +447,7 @@ public void testCannotRemoveServiceAccountFromGroupViaGroupPatch() { } // Verify service account is still in the group (operation was rejected) - UserResource userResource = realm.admin().users().get(serviceAccount.getId()); + UserResource userResource = realm.admin().users().get(serviceAccountUserId); List groups = Optional.ofNullable(userResource.groups()).orElse(List.of()).stream().map(GroupRepresentation::getId).toList(); assertTrue(groups.contains(regularGroupId), "Service account should still be in group after failed PATCH"); } @@ -474,12 +474,12 @@ public void testServiceAccountNotDiscoverableViaGroupMembersFilter() { String groupId = group.getId(); realm.cleanup().add(r -> r.groups().group(groupId).remove()); - UserRepresentation serviceAccount = realm.admin().clients().get(clientDbId).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(clientDbId).toRepresentation().getServiceAccountUserId(); // Add only the service account to the group via Admin API (bypassing SCIM protection) - realm.admin().users().get(serviceAccount.getId()).joinGroup(groupId); + realm.admin().users().get(serviceAccountUserId).joinGroup(groupId); // A service account must not be discoverable by its id (eq) - assertGroupNotMatched(groupId, ResourceFilter.filter().eq("members.value", serviceAccount.getId()).build(), + assertGroupNotMatched(groupId, ResourceFilter.filter().eq("members.value", serviceAccountUserId).build(), "Service account must not be discoverable via members.value eq"); // A group whose only member is a service account must appear empty (pr) assertGroupNotMatched(groupId, ResourceFilter.filter().pr("members").build(), diff --git a/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/AuthorizationTest.java b/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/AuthorizationTest.java index 0665bd54630a..86f59e2261ce 100644 --- a/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/AuthorizationTest.java +++ b/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/AuthorizationTest.java @@ -309,10 +309,10 @@ public void testAllowUsersFromGroupFGAP() { GroupRepresentation group = createGroup(); managedUser.admin().joinGroup(group.getId()); ClientRepresentation client = getScimClient(); - UserRepresentation serviceAccount = realm.admin().clients().get(client.getId()).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(client.getId()).toRepresentation().getServiceAccountUserId(); UserPolicyRepresentation policy = new UserPolicyRepresentation(); policy.setName("Allow SCIM access"); - policy.addUser(serviceAccount.getId()); + policy.addUser(serviceAccountUserId); ClientResource permissionClient = realm.admin().clients().get(realm.admin().clients().findByClientId(Constants.ADMIN_PERMISSIONS_CLIENT_ID).get(0).getId()); permissionClient.authorization().policies().user().create(policy).close(); ScopePermissionRepresentation permission = new ScopePermissionRepresentation(); @@ -338,10 +338,10 @@ public void testAllowGroupsFGAP() { GroupRepresentation group = createGroup(); ClientRepresentation client = getScimClient(); - UserRepresentation serviceAccount = realm.admin().clients().get(client.getId()).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(client.getId()).toRepresentation().getServiceAccountUserId(); UserPolicyRepresentation policy = new UserPolicyRepresentation(); policy.setName("Allow SCIM access"); - policy.addUser(serviceAccount.getId()); + policy.addUser(serviceAccountUserId); ClientResource permissionClient = realm.admin().clients().get(realm.admin().clients().findByClientId(Constants.ADMIN_PERMISSIONS_CLIENT_ID).get(0).getId()); permissionClient.authorization().policies().user().create(policy).close(); ScopePermissionRepresentation permission = new ScopePermissionRepresentation(); @@ -517,12 +517,12 @@ public void testGroupMembershipDeniedWithoutManageGroupMembershipFGAP() { GroupRepresentation group = createGroup(); ClientRepresentation client = getScimClient(); - UserRepresentation serviceAccount = realm.admin().clients().get(client.getId()).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(client.getId()).toRepresentation().getServiceAccountUserId(); // Create policy for the SCIM service account UserPolicyRepresentation policy = new UserPolicyRepresentation(); policy.setName("Allow SCIM user management"); - policy.addUser(serviceAccount.getId()); + policy.addUser(serviceAccountUserId); ClientResource permissionClient = realm.admin().clients().get( realm.admin().clients().findByClientId(Constants.ADMIN_PERMISSIONS_CLIENT_ID).get(0).getId()); permissionClient.authorization().policies().user().create(policy).close(); @@ -622,12 +622,12 @@ public void testGroupMembersAddDeniedWithoutManageMembershipFGAP() { GroupRepresentation group = createGroup(); ClientRepresentation client = getScimClient(); - UserRepresentation serviceAccount = realm.admin().clients().get(client.getId()).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(client.getId()).toRepresentation().getServiceAccountUserId(); // Create policy for the SCIM service account UserPolicyRepresentation policy = new UserPolicyRepresentation(); policy.setName("Allow SCIM access"); - policy.addUser(serviceAccount.getId()); + policy.addUser(serviceAccountUserId); ClientResource permissionClient = realm.admin().clients().get( realm.admin().clients().findByClientId(Constants.ADMIN_PERMISSIONS_CLIENT_ID).get(0).getId()); permissionClient.authorization().policies().user().create(policy).close(); @@ -664,12 +664,12 @@ public void testGroupMembersRemoveDeniedWithoutManageMembershipFGAP() { managedUser.admin().joinGroup(group.getId()); ClientRepresentation client = getScimClient(); - UserRepresentation serviceAccount = realm.admin().clients().get(client.getId()).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(client.getId()).toRepresentation().getServiceAccountUserId(); // Create policy for the SCIM service account UserPolicyRepresentation policy = new UserPolicyRepresentation(); policy.setName("Allow SCIM access"); - policy.addUser(serviceAccount.getId()); + policy.addUser(serviceAccountUserId); ClientResource permissionClient = realm.admin().clients().get( realm.admin().clients().findByClientId(Constants.ADMIN_PERMISSIONS_CLIENT_ID).get(0).getId()); permissionClient.authorization().policies().user().create(policy).close(); @@ -700,12 +700,12 @@ public void testGroupMembersRemoveDeniedWithoutManageGroupMembershipOnUserFGAP() managedUser.admin().joinGroup(group.getId()); ClientRepresentation client = getScimClient(); - UserRepresentation serviceAccount = realm.admin().clients().get(client.getId()).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(client.getId()).toRepresentation().getServiceAccountUserId(); // Create policy for the SCIM service account UserPolicyRepresentation policy = new UserPolicyRepresentation(); policy.setName("Allow SCIM access"); - policy.addUser(serviceAccount.getId()); + policy.addUser(serviceAccountUserId); ClientResource permissionClient = realm.admin().clients().get( realm.admin().clients().findByClientId(Constants.ADMIN_PERMISSIONS_CLIENT_ID).get(0).getId()); permissionClient.authorization().policies().user().create(policy).close(); @@ -741,12 +741,12 @@ public void testGroupMembersAllowedWithAllFGAPScopes() { GroupRepresentation group = createGroup(); ClientRepresentation client = getScimClient(); - UserRepresentation serviceAccount = realm.admin().clients().get(client.getId()).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(client.getId()).toRepresentation().getServiceAccountUserId(); // Create policy for the SCIM service account UserPolicyRepresentation policy = new UserPolicyRepresentation(); policy.setName("Allow SCIM access"); - policy.addUser(serviceAccount.getId()); + policy.addUser(serviceAccountUserId); ClientResource permissionClient = realm.admin().clients().get( realm.admin().clients().findByClientId(Constants.ADMIN_PERMISSIONS_CLIENT_ID).get(0).getId()); permissionClient.authorization().policies().user().create(policy).close(); @@ -797,12 +797,12 @@ public void testGroupMembersAddDeniedWithoutManageGroupMembershipOnUserFGAP() { GroupRepresentation group = createGroup(); ClientRepresentation client = getScimClient(); - UserRepresentation serviceAccount = realm.admin().clients().get(client.getId()).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(client.getId()).toRepresentation().getServiceAccountUserId(); // Create policy for the SCIM service account UserPolicyRepresentation policy = new UserPolicyRepresentation(); policy.setName("Allow SCIM access"); - policy.addUser(serviceAccount.getId()); + policy.addUser(serviceAccountUserId); ClientResource permissionClient = realm.admin().clients().get( realm.admin().clients().findByClientId(Constants.ADMIN_PERMISSIONS_CLIENT_ID).get(0).getId()); permissionClient.authorization().policies().user().create(policy).close(); @@ -1115,11 +1115,11 @@ public void testAccessDeniedWithoutAudienceMapping() { clientDbId = ApiUtil.getCreatedId(response); } - UserRepresentation serviceAccountUser = realm.admin().clients().get(clientDbId).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(clientDbId).toRepresentation().getServiceAccountUserId(); ClientRepresentation realmMgmt = realm.admin().clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0); RoleRepresentation manageUsersRole = realm.admin().clients().get(realmMgmt.getId()).roles() .get(AdminRoles.MANAGE_USERS).toRepresentation(); - realm.admin().users().get(serviceAccountUser.getId()).roles() + realm.admin().users().get(serviceAccountUserId).roles() .clientLevel(realmMgmt.getId()).add(List.of(manageUsersRole)); realm.admin().clients().get(clientDbId).getScopeMappings() .clientLevel(realmMgmt.getId()).add(List.of(manageUsersRole)); @@ -1210,11 +1210,11 @@ public void testSubRealmAdminTreatedAsAdminInMasterRealm() { try { ClientRepresentation masterRealmMgmt = adminClient.realm("master").clients() .findByClientId("master-realm").get(0); - UserRepresentation scimServiceAccount = adminClient.realm("master").clients() - .get(scimMasterClient.getId()).getServiceAccountUser(); + String scimServiceAccountUserId = adminClient.realm("master").clients() + .get(scimMasterClient.getId()).toRepresentation().getServiceAccountUserId(); RoleRepresentation manageUsersRole = adminClient.realm("master").clients() .get(masterRealmMgmt.getId()).roles().get(AdminRoles.MANAGE_USERS).toRepresentation(); - adminClient.realm("master").users().get(scimServiceAccount.getId()) + adminClient.realm("master").users().get(scimServiceAccountUserId) .roles().clientLevel(masterRealmMgmt.getId()).add(List.of(manageUsersRole)); adminClient.realm("master").clients().get(scimMasterClient.getId()) .getScopeMappings().clientLevel(masterRealmMgmt.getId()).add(List.of(manageUsersRole)); @@ -1490,7 +1490,8 @@ private UserPolicyRepresentation createUserPolicy(String userId) { } private UserRepresentation getServiceAccount() { - return realm.admin().clients().get(getScimClient().getId()).getServiceAccountUser(); + String saUserId = realm.admin().clients().get(getScimClient().getId()).toRepresentation().getServiceAccountUserId(); + return realm.admin().users().get(saUserId).toRepresentation(); } private ScopePermissionRepresentation createPermission(String resourceType, Set resources, Set scopes, String policyName) { @@ -1526,7 +1527,8 @@ private void assertAccessUnauthorized(Runnable action) { private void grantAdminRole(String role) { ClientRepresentation clientRep = getScimClient(); - UserRepresentation serviceAccountUser = realm.admin().clients().get(clientRep.getId()).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(clientRep.getId()).toRepresentation().getServiceAccountUserId(); + UserRepresentation serviceAccountUser = realm.admin().users().get(serviceAccountUserId).toRepresentation(); grantAdminRole(role, serviceAccountUser, clientRep.getId()); } @@ -1669,7 +1671,7 @@ private void revokeAdminRole(String name) { ClientRepresentation realmMgmt = realm.admin().clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0); RoleRepresentation role = realm.admin().clients().get(realmMgmt.getId()).roles().get(name).toRepresentation(); ClientRepresentation clientRep = getScimClient(); - UserRepresentation serviceAccountUser = realm.admin().clients().get(clientRep.getId()).getServiceAccountUser(); - realm.admin().users().get(serviceAccountUser.getId()).roles().clientLevel(realmMgmt.getId()).remove(List.of(role)); + String serviceAccountUserId = realm.admin().clients().get(clientRep.getId()).toRepresentation().getServiceAccountUserId(); + realm.admin().users().get(serviceAccountUserId).roles().clientLevel(realmMgmt.getId()).remove(List.of(role)); } } diff --git a/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/GroupTest.java b/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/GroupTest.java index 112cce22755b..d0e562ca955a 100644 --- a/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/GroupTest.java +++ b/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/GroupTest.java @@ -19,7 +19,6 @@ import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.OrganizationDomainRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation; -import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.scim.client.ResourceFilter; import org.keycloak.scim.client.ScimClientException; import org.keycloak.scim.protocol.request.PatchRequest; @@ -1167,12 +1166,12 @@ public void testCannotCreateGroupWithServiceAccountMember() { } realm.cleanup().add(r -> r.clients().get(clientDbId).remove()); - UserRepresentation serviceAccount = realm.admin().clients().get(clientDbId).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(clientDbId).toRepresentation().getServiceAccountUserId(); // Try to create a group with service account in members Group newGroup = new Group(); newGroup.setDisplayName("test-sa-group-" + KeycloakModelUtils.generateId()); - newGroup.addMember(serviceAccount.getId()); + newGroup.addMember(serviceAccountUserId); try { client.groups().create(newGroup); @@ -1201,10 +1200,10 @@ public void testCanRemoveRegularMemberWhenServiceAccountIsAlsoMember() { group.setDisplayName("test-group-" + KeycloakModelUtils.generateId()); group = client.groups().create(group); - UserRepresentation serviceAccount = realm.admin().clients().get(clientDbId).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(clientDbId).toRepresentation().getServiceAccountUserId(); // Add both service account and regular user to group via Admin API - realm.admin().users().get(serviceAccount.getId()).joinGroup(group.getId()); + realm.admin().users().get(serviceAccountUserId).joinGroup(group.getId()); realm.admin().users().get(regularUser.getId()).joinGroup(group.getId()); // Verify regular user is removed from group @@ -1228,7 +1227,7 @@ public void testCanRemoveRegularMemberWhenServiceAccountIsAlsoMember() { assertNull(updated.getMembers(), "Members list should be null"); // Verify service account is still in the group (using Admin API, since SCIM filters them out) - List groupIds = Optional.ofNullable(realm.admin().users().get(serviceAccount.getId()).groups()).orElse(List.of()).stream().map(GroupRepresentation::getId).toList(); + List groupIds = Optional.ofNullable(realm.admin().users().get(serviceAccountUserId).groups()).orElse(List.of()).stream().map(GroupRepresentation::getId).toList(); assertTrue(groupIds.contains(group.getId()), "Service account should still be in group"); } diff --git a/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/UserTest.java b/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/UserTest.java index e46760e33fb7..9bad8918f8fa 100644 --- a/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/UserTest.java +++ b/scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/UserTest.java @@ -417,8 +417,8 @@ public void testGetByIdServiceAccount() { } realm.cleanup().add(realm -> realm.clients().get(targetClient.getId()).remove()); - UserRepresentation serviceAccount = realm.admin().clients().get(targetClient.getId()).getServiceAccountUser(); - User actual = client.users().get(serviceAccount.getId()); + String serviceAccountUserId = realm.admin().clients().get(targetClient.getId()).toRepresentation().getServiceAccountUserId(); + User actual = client.users().get(serviceAccountUserId); assertNull(actual); } @@ -437,8 +437,8 @@ public void testQueryByIdServiceAccount() { } realm.cleanup().add(realm -> realm.clients().get(targetClient.getId()).remove()); - UserRepresentation serviceAccount = realm.admin().clients().get(targetClient.getId()).getServiceAccountUser(); - ListResponse response = client.users().getAll(ResourceFilter.filter().eq("id", serviceAccount.getId()).build()); + String serviceAccountUserId = realm.admin().clients().get(targetClient.getId()).toRepresentation().getServiceAccountUserId(); + ListResponse response = client.users().getAll(ResourceFilter.filter().eq("id", serviceAccountUserId).build()); assertEquals(0, response.getTotalResults()); } @@ -457,7 +457,8 @@ public void testUpdateServiceAccountIsRejected() { } realm.cleanup().add(realm -> realm.clients().get(targetClient.getId()).remove()); - UserRepresentation serviceAccount = realm.admin().clients().get(targetClient.getId()).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(targetClient.getId()).toRepresentation().getServiceAccountUserId(); + UserRepresentation serviceAccount = realm.admin().users().get(serviceAccountUserId).toRepresentation(); User update = new User(); update.setId(serviceAccount.getId()); update.setUserName(serviceAccount.getUsername()); @@ -484,10 +485,10 @@ public void testDeleteServiceAccountIsRejected() { } realm.cleanup().add(realm -> realm.clients().get(targetClient.getId()).remove()); - UserRepresentation serviceAccount = realm.admin().clients().get(targetClient.getId()).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(targetClient.getId()).toRepresentation().getServiceAccountUserId(); try { - client.users().delete(serviceAccount.getId()); + client.users().delete(serviceAccountUserId); fail("Should not be able to delete a service account via SCIM"); } catch (ScimClientException sce) { assertEquals(Status.NOT_FOUND.getStatusCode(), sce.getError().getStatusInt()); @@ -508,10 +509,10 @@ public void testPatchServiceAccountIsRejected() { } realm.cleanup().add(realm -> realm.clients().get(targetClient.getId()).remove()); - UserRepresentation serviceAccount = realm.admin().clients().get(targetClient.getId()).getServiceAccountUser(); + String serviceAccountUserId = realm.admin().clients().get(targetClient.getId()).toRepresentation().getServiceAccountUserId(); try { - client.users().patch(serviceAccount.getId(), PatchRequest.create() + client.users().patch(serviceAccountUserId, PatchRequest.create() .add("name.givenName", "modified") .build()); fail("Should not be able to patch a service account via SCIM"); diff --git a/scim/tests/scim-client/src/main/java/org/keycloak/testframework/scim/client/ScimClientSupplier.java b/scim/tests/scim-client/src/main/java/org/keycloak/testframework/scim/client/ScimClientSupplier.java index 984bb8b411f3..3c7870de3527 100644 --- a/scim/tests/scim-client/src/main/java/org/keycloak/testframework/scim/client/ScimClientSupplier.java +++ b/scim/tests/scim-client/src/main/java/org/keycloak/testframework/scim/client/ScimClientSupplier.java @@ -10,7 +10,6 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RoleRepresentation; -import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.scim.client.ScimClient; import org.keycloak.scim.client.authorization.OAuth2Bearer; import org.keycloak.testframework.injection.DependenciesBuilder; @@ -58,10 +57,10 @@ public ScimClient getValue(InstanceContext instanc .enabled(true) .build())) { String id = ApiUtil.getCreatedId(response); - UserRepresentation serviceAccountUser = managedRealm.admin().clients().get(id).getServiceAccountUser(); + String serviceAccountUserId = managedRealm.admin().clients().get(id).toRepresentation().getServiceAccountUserId(); ClientRepresentation realmMgmtClient = managedRealm.admin().clients().findByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID).get(0); RoleRepresentation manageUsersRole = managedRealm.admin().clients().get(realmMgmtClient.getId()).roles().get(AdminRoles.MANAGE_USERS).toRepresentation(); - managedRealm.admin().users().get(serviceAccountUser.getId()).roles() + managedRealm.admin().users().get(serviceAccountUserId).roles() .clientLevel(realmMgmtClient.getId()) .add(List.of(manageUsersRole)); managedRealm.admin().clients().get(id).getScopeMappings() diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index eeec0adacf62..d2f7da9baf82 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -1588,4 +1588,11 @@ public static IssuedVerifiableCredentialRepresentation toRepresentation(IssuedVe return toRepresentation(model, credentialType); } + public static void setServiceAccountUserId(KeycloakSession session, ClientModel client, ClientRepresentation rep) { + UserModel serviceAccount = session.users().getServiceAccount(client); + if (serviceAccount != null) { + rep.setServiceAccountUserId(serviceAccount.getId()); + } + } + } diff --git a/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java b/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java index f109dd9a495d..44b7616a6b99 100644 --- a/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java +++ b/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java @@ -21,9 +21,13 @@ import java.util.List; import org.keycloak.connections.jpa.support.EntityManagers; +import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.PartialImportRepresentation; +import org.keycloak.services.managers.ClientManager; +import org.keycloak.services.managers.RealmManager; /** * This class manages the PartialImport handlers. @@ -65,7 +69,24 @@ public PartialImportResults saveResources() { results.addAllResults(partialImport.doImport(rep, realm, session)); } + ensureServiceAccountsExist(results); + return results; } + private void ensureServiceAccountsExist(PartialImportResults results) { + ClientManager clientManager = new ClientManager(new RealmManager(session)); + for (PartialImportResult result : results.getResults()) { + if (result.getResourceType() != ResourceType.CLIENT + || (result.getAction() != Action.ADDED && result.getAction() != Action.OVERWRITTEN)) { + continue; + } + ClientRepresentation clientRep = (ClientRepresentation) result.getRepresentation(); + ClientModel client = realm.getClientByClientId(clientRep.getClientId()); + if (client != null && client.isServiceAccountsEnabled() && session.users().getServiceAccount(client) == null) { + clientManager.enableServiceAccount(client); + } + } + } + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index 11012bc0be46..d4330945dcb6 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -61,7 +61,6 @@ import org.keycloak.models.ModelValidationException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.ModelToRepresentation; @@ -76,7 +75,6 @@ import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.ManagementPermissionReference; -import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponseException; @@ -225,6 +223,10 @@ public ClientRepresentation getClient() { representation.setAccess(auth.clients().getAccess(client)); + if (client.isServiceAccountsEnabled()) { + ModelToRepresentation.setServiceAccountUserId(session, client, representation); + } + return representation; } @@ -522,26 +524,6 @@ public ClientScopeEvaluateResource clientScopeEvaluateResource() { return new ClientScopeEvaluateResource(session, session.getContext().getUri(), realm, auth, client, clientConnection); } - /** - * Get a user dedicated to the service account - * - * @return - */ - @Path("service-account-user") - @GET - @NoCache - @Produces(MediaType.APPLICATION_JSON) - @Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENTS) - @Operation( summary = "Get a user dedicated to the service account") - public UserRepresentation getServiceAccountUser() { - auth.clients().requireView(client); - - UserModel user = new ClientManager(new RealmManager(session)).getServiceAccountUser(client) - .orElseThrow(() -> new BadRequestException("Service account not enabled for the client '" + client.getClientId() + "'")); - - return ModelToRepresentation.toRepresentation(session, realm, user); - } - /** * Push the client's revocation policy to its admin URL * diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index 6d5c41d7a79e..97603134d7fc 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -135,6 +135,9 @@ public Stream getClients(@Parameter(description = "filter StripSecretsUtils.stripClient(representation); } representation.setAccess(auth.clients().getAccess(c)); + if (c.isServiceAccountsEnabled()) { + ModelToRepresentation.setServiceAccountUserId(session, c, representation); + } return representation; }); } diff --git a/ssf/tests/base/src/test/java/org/keycloak/tests/ssf/transmitter/SsfTransmitterEventEmitterTests.java b/ssf/tests/base/src/test/java/org/keycloak/tests/ssf/transmitter/SsfTransmitterEventEmitterTests.java index bb384f928276..261d17891504 100644 --- a/ssf/tests/base/src/test/java/org/keycloak/tests/ssf/transmitter/SsfTransmitterEventEmitterTests.java +++ b/ssf/tests/base/src/test/java/org/keycloak/tests/ssf/transmitter/SsfTransmitterEventEmitterTests.java @@ -24,7 +24,6 @@ import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation; import org.keycloak.representations.idm.RoleRepresentation; -import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.ssf.Ssf; import org.keycloak.ssf.event.caep.CaepCredentialChange; import org.keycloak.ssf.event.caep.CaepSessionRevoked; @@ -1375,14 +1374,14 @@ protected void ensureReceiverRole(String roleName) { protected void grantRoleToServiceAccount(String mgmtClientId, String targetClientId, String roleName) { ClientRepresentation mgmt = findClientByClientId(mgmtClientId); - UserRepresentation saUser = realm.admin().clients().get(mgmt.getId()).getServiceAccountUser(); + String saUserId = realm.admin().clients().get(mgmt.getId()).toRepresentation().getServiceAccountUserId(); String targetUuid = findClientByClientId(targetClientId).getId(); RoleRepresentation role = realm.admin().clients().get(targetUuid).roles().get(roleName).toRepresentation(); // The admin API is idempotent for add() — if the mapping already // exists it's a no-op, which is what we want for @BeforeEach. - realm.admin().users().get(saUser.getId()).roles().clientLevel(targetUuid).add(List.of(role)); + realm.admin().users().get(saUserId).roles().clientLevel(targetUuid).add(List.of(role)); } protected ClientRepresentation findClientByClientId(String clientId) { diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/ClientTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/ClientTest.java index e12c08b34ffd..8d86f2f84315 100644 --- a/tests/base/src/test/java/org/keycloak/tests/admin/ClientTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/admin/ClientTest.java @@ -844,7 +844,8 @@ public void serviceAccount() { Response response = managedRealm.admin().clients().create(ClientBuilder.create().clientId("serviceClient").serviceAccountsEnabled(true).build()); String id = ApiUtil.getCreatedId(response); managedRealm.cleanup().add(r -> r.clients().get(id).remove()); - UserRepresentation userRep = managedRealm.admin().clients().get(id).getServiceAccountUser(); + String saUserId = managedRealm.admin().clients().get(id).toRepresentation().getServiceAccountUserId(); + UserRepresentation userRep = managedRealm.admin().users().get(saUserId).toRepresentation(); MatcherAssert.assertThat("service-account-serviceclient", Matchers.equalTo(userRep.getUsername())); // KEYCLOAK-11197 service accounts are no longer created with a placeholder e-mail. assertNull(userRep.getEmail()); diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/ImpersonationTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/ImpersonationTest.java index 248f735878b9..127c3dfaab00 100644 --- a/tests/base/src/test/java/org/keycloak/tests/admin/ImpersonationTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/admin/ImpersonationTest.java @@ -293,7 +293,8 @@ public void testImpersonationBySameRealmServiceAccount() throws Exception { } managedRealm.cleanup().add(r -> r.clients().get(clientId).remove()); - UserRepresentation user = AdminApiUtil.findClientByClientId(managedRealm.admin(), "service-account-cl").getServiceAccountUser(); + String saUserId = AdminApiUtil.findClientByClientId(managedRealm.admin(), "service-account-cl").toRepresentation().getServiceAccountUserId(); + UserRepresentation user = managedRealm.admin().users().get(saUserId).toRepresentation(); user.setServiceAccountClientId("service-account-cl"); // add impersonation roles @@ -319,7 +320,8 @@ public void testImpersonationByMasterRealmServiceAccount() throws Exception { } masterRealm.cleanup().add(r -> r.clients().get(clientId).remove()); - UserRepresentation user = AdminApiUtil.findClientByClientId(masterRealm.admin(), "service-account-cl").getServiceAccountUser(); + String saUserId = AdminApiUtil.findClientByClientId(masterRealm.admin(), "service-account-cl").toRepresentation().getServiceAccountUserId(); + UserRepresentation user = masterRealm.admin().users().get(saUserId).toRepresentation(); user.setServiceAccountClientId("service-account-cl"); // add impersonation roles diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/PermissionsTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/PermissionsTest.java index 681b6bb6a55c..b449b1293fe4 100644 --- a/tests/base/src/test/java/org/keycloak/tests/admin/PermissionsTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/admin/PermissionsTest.java @@ -163,7 +163,6 @@ public void clients() { invoke(realm -> realm.clients().get(foo.getId()).regenerateRegistrationAccessToken(), Resource.CLIENT, true); invoke(realm -> realm.clients().get(foo.getId()).getSecret(), Resource.CLIENT, true); invoke(realm -> realm.clients().get(foo.getId()).getClientRotatedSecret(), Resource.CLIENT, true); - invoke(realm -> realm.clients().get(foo.getId()).getServiceAccountUser(), Resource.CLIENT, false); invoke(realm -> realm.clients().get(foo.getId()).pushRevocation(), Resource.CLIENT, true); invoke(realm -> realm.clients().get(foo.getId()).getApplicationSessionCount(), Resource.CLIENT, false); invoke(realm -> realm.clients().get(foo.getId()).getUserSessions(0, 100), Resource.CLIENT, false); diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/client/ServiceAccountClientTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/client/ServiceAccountClientTest.java index 786850254a00..037501ced250 100644 --- a/tests/base/src/test/java/org/keycloak/tests/admin/client/ServiceAccountClientTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/admin/client/ServiceAccountClientTest.java @@ -19,11 +19,18 @@ import java.util.stream.Collectors; -import org.keycloak.admin.client.resource.ClientScopeResource; +import jakarta.ws.rs.ForbiddenException; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.common.constants.ServiceAccountConstants; +import org.keycloak.models.AdminRoles; +import org.keycloak.models.Constants; import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientScopeRepresentation; -import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testframework.admin.AdminClientFactory; +import org.keycloak.testframework.annotations.InjectAdminClientFactory; import org.keycloak.testframework.annotations.InjectClient; import org.keycloak.testframework.annotations.InjectRealm; import org.keycloak.testframework.annotations.KeycloakIntegrationTest; @@ -33,6 +40,9 @@ import org.keycloak.testframework.realm.ClientConfig; import org.keycloak.testframework.realm.ManagedClient; import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.RealmBuilder; +import org.keycloak.testframework.realm.RealmConfig; +import org.keycloak.testframework.realm.UserBuilder; import org.keycloak.tests.utils.admin.AdminApiUtil; import org.keycloak.testsuite.util.oauth.AccessTokenResponse; @@ -47,7 +57,11 @@ @KeycloakIntegrationTest public class ServiceAccountClientTest { - @InjectRealm + private static final String clientId = "service-account-client"; + private static final String VIEW_CLIENTS_USER = "view-clients-user"; + private static final String PASSWORD = "password"; + + @InjectRealm(config = ServiceAccountRealmConfig.class) ManagedRealm managedRealm; @InjectClient(config = ServiceAccountClientConfig.class) @@ -56,11 +70,12 @@ public class ServiceAccountClientTest { @InjectOAuthClient OAuthClient oAuthClient; - private static final String clientId = "service-account-client"; + @InjectAdminClientFactory + AdminClientFactory adminClientFactory; @Test public void testServiceAccountEnableDisable() { - ClientScopeResource serviceAccountScopeRsc = AdminApiUtil.findClientScopeByName( + org.keycloak.admin.client.resource.ClientScopeResource serviceAccountScopeRsc = AdminApiUtil.findClientScopeByName( managedRealm.admin(), ServiceAccountConstants.SERVICE_ACCOUNT_SCOPE); Assertions.assertNotNull(serviceAccountScopeRsc); ClientScopeRepresentation serviceAccountScope = serviceAccountScopeRsc.toRepresentation(); @@ -124,12 +139,35 @@ public void testServiceAccountEnableDisable() { Assertions.assertNotNull(accessToken.getOtherClaims().get(ServiceAccountConstants.CLIENT_ADDRESS)); // remove the service account and client credentials should fail - UserRepresentation serviceAccountUser = managedClient.admin().getServiceAccountUser(); - managedRealm.admin().users().delete(serviceAccountUser.getId()).close(); + String serviceAccountUserId = managedClient.admin().toRepresentation().getServiceAccountUserId(); + managedRealm.admin().users().delete(serviceAccountUserId).close(); response = oAuthClient.doClientCredentialsGrantAccessTokenRequest(); Assertions.assertEquals("invalid_request", response.getError()); } + @Test + public void testServiceAccountUserIdVisibleWithViewClientsOnly() { + try (Keycloak viewClientsAdmin = adminClientFactory.create() + .realm(managedRealm.getName()) + .username(VIEW_CLIENTS_USER) + .password(PASSWORD) + .clientId(Constants.ADMIN_CLI_CLIENT_ID) + .build()) { + + ClientRepresentation client = viewClientsAdmin.realm(managedRealm.getName()) + .clients().findByClientId(clientId).get(0); + ClientResource clientResource = viewClientsAdmin.realm(managedRealm.getName()) + .clients().get(client.getId()); + + ClientRepresentation fullClient = clientResource.toRepresentation(); + Assertions.assertNotNull(fullClient.getServiceAccountUserId()); + + Assertions.assertThrows(ForbiddenException.class, () -> + viewClientsAdmin.realm(managedRealm.getName()) + .users().get(fullClient.getServiceAccountUserId()).toRepresentation()); + } + } + private static class ServiceAccountClientConfig implements ClientConfig { @Override @@ -143,4 +181,19 @@ public ClientBuilder configure(ClientBuilder client) { .publicClient(false); } } + + private static class ServiceAccountRealmConfig implements RealmConfig { + + @Override + public RealmBuilder configure(RealmBuilder realm) { + realm.users(UserBuilder.create(VIEW_CLIENTS_USER) + .password(PASSWORD) + .email(VIEW_CLIENTS_USER + "@localhost") + .firstName("View") + .lastName("Clients") + .clientRoles(Constants.REALM_MANAGEMENT_CLIENT_ID, + AdminRoles.VIEW_CLIENTS)); + return realm; + } + } } diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/partialimport/PartialImportClientTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/partialimport/PartialImportClientTest.java index 850adb0f91ae..8c6a87cfa43b 100644 --- a/tests/base/src/test/java/org/keycloak/tests/admin/partialimport/PartialImportClientTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/admin/partialimport/PartialImportClientTest.java @@ -188,7 +188,7 @@ public void testOverwriteExistingClientWithServiceAccount() { Assertions.assertEquals(1, doImport().getOverwritten()); ClientRepresentation client = managedRealm.admin().clients().findByClientId(CLIENT_SERVICE_ACCOUNT).get(0); - Assertions.assertDoesNotThrow(() -> managedRealm.admin().clients().get(client.getId()).getServiceAccountUser()); + Assertions.assertNotNull(managedRealm.admin().clients().get(client.getId()).toRepresentation().getServiceAccountUserId()); } @Test diff --git a/tests/base/src/test/java/org/keycloak/tests/client/ClientRegistrationTest.java b/tests/base/src/test/java/org/keycloak/tests/client/ClientRegistrationTest.java index d205cbdd69c0..5fb5c1193cc0 100644 --- a/tests/base/src/test/java/org/keycloak/tests/client/ClientRegistrationTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/client/ClientRegistrationTest.java @@ -56,7 +56,6 @@ import org.keycloak.representations.idm.OAuth2ErrorRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RoleRepresentation; -import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; @@ -136,14 +135,14 @@ public void withServiceAccount() throws ClientRegistrationException { ClientRepresentation rep = registerClient(clientRep, false); - UserRepresentation serviceAccountUser = managedRealm.admin().clients().get(rep.getId()).getServiceAccountUser(); + String serviceAccountUserId = managedRealm.admin().clients().get(rep.getId()).toRepresentation().getServiceAccountUserId(); - assertNotNull(serviceAccountUser); + assertNotNull(serviceAccountUserId); deleteClient(rep); try { - managedRealm.admin().users().get(serviceAccountUser.getId()).toRepresentation(); + managedRealm.admin().users().get(serviceAccountUserId).toRepresentation(); fail("Expected NotFoundException"); } catch (NotFoundException e) { } @@ -997,9 +996,9 @@ public void registerWithLightweightAccessTokenAndTransientSession() throws Excep Response response = masterRealm.admin().clients().create(testAdminClient); testAdminClient = masterRealm.admin().clients().get( ApiUtil.getCreatedId(response)).toRepresentation(); - UserRepresentation serviceAccountUser = masterRealm.admin().clients().get(testAdminClient.getId()).getServiceAccountUser(); + String serviceAccountUserId = testAdminClient.getServiceAccountUserId(); RoleRepresentation adminRole = masterRealm.admin().roles().get(AdminRoles.ADMIN).toRepresentation(); - masterRealm.admin().users().get(serviceAccountUser.getId()).roles().realmLevel().add(List.of(adminRole)); + masterRealm.admin().users().get(serviceAccountUserId).roles().realmLevel().add(List.of(adminRole)); oauth.client(TEST_ADMIN_CLIENT, TEST_ADMIN_CLIENT); oauth.realm(masterRealm.getName()); diff --git a/tests/base/src/test/java/org/keycloak/tests/client/OIDCClientRegistrationCorsTest.java b/tests/base/src/test/java/org/keycloak/tests/client/OIDCClientRegistrationCorsTest.java index eca1f8fd419c..5cd4a99b1e03 100644 --- a/tests/base/src/test/java/org/keycloak/tests/client/OIDCClientRegistrationCorsTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/client/OIDCClientRegistrationCorsTest.java @@ -76,7 +76,7 @@ public void testWithServiceAccount() throws IOException { RoleRepresentation createClientRoleRep = realm.admin().clients().get(realmManagementId).roles().get("create-client").toRepresentation(); Response response = realm.admin().clients().create(ClientBuilder.create().clientId("testWithServiceAccount").secret("secret").serviceAccountsEnabled(true).webOrigins("https://origin1").build()); String clientUuid = CreatedResponseUtil.getCreatedId(response); - String serviceAccountUuid = realm.admin().clients().get(clientUuid).getServiceAccountUser().getId(); + String serviceAccountUuid = realm.admin().clients().get(clientUuid).toRepresentation().getServiceAccountUserId(); realm.admin().users().get(serviceAccountUuid).roles().clientLevel(realmManagementId).add(List.of(createClientRoleRep)); String accessToken = oAuthClient.client("testWithServiceAccount", "secret").doClientCredentialsGrantAccessTokenRequest().getAccessToken(); diff --git a/tests/base/src/test/java/org/keycloak/tests/client/SAMLClientRegistrationTest.java b/tests/base/src/test/java/org/keycloak/tests/client/SAMLClientRegistrationTest.java index 3144283e8440..6e1000395d9a 100644 --- a/tests/base/src/test/java/org/keycloak/tests/client/SAMLClientRegistrationTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/client/SAMLClientRegistrationTest.java @@ -83,7 +83,7 @@ public void createClient() throws ClientRegistrationException, IOException { public void testSAMLEndpointCreateWithOIDCClient() throws Exception { ClientsResource clientsResource = managedRealm.admin().clients(); ClientRepresentation oidcClient = clientsResource.findByClientId("oidc-client").get(0); - String oidcClientServiceId = clientsResource.get(oidcClient.getId()).getServiceAccountUser().getId(); + String oidcClientServiceId = clientsResource.get(oidcClient.getId()).toRepresentation().getServiceAccountUserId(); String realmManagementId = clientsResource.findByClientId("realm-management").get(0).getId(); RoleRepresentation role = clientsResource.get(realmManagementId).roles().get("create-client").toRepresentation(); diff --git a/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientAgentDelegationTest.java b/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientAgentDelegationTest.java index 700179a5e017..fb48cbfb10ab 100644 --- a/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientAgentDelegationTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientAgentDelegationTest.java @@ -554,7 +554,7 @@ private String getActorToken() { } private String getServiceAccountUserId() { - return actorApp.admin().getServiceAccountUser().getId(); + return actorApp.admin().toRepresentation().getServiceAccountUserId(); } private void logout(String refreshToken) { diff --git a/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientDelegationCibaTest.java b/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientDelegationCibaTest.java index 320cc44d816f..9839973eec0d 100644 --- a/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientDelegationCibaTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientDelegationCibaTest.java @@ -282,7 +282,7 @@ private String getActorToken() { private String getServiceAccountUserId() { return realm.admin().clients().findByClientId(AGENT_CLIENT_ID).stream() .findFirst() - .map(c -> realm.admin().clients().get(c.getId()).getServiceAccountUser().getId()) + .map(c -> realm.admin().clients().get(c.getId()).toRepresentation().getServiceAccountUserId()) .orElseThrow(() -> new AssertionError("agent-app client not found")); } diff --git a/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientDelegationTest.java b/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientDelegationTest.java index 4b0c97f3bf3c..2880b4f45d2f 100644 --- a/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientDelegationTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/ClientDelegationTest.java @@ -534,11 +534,11 @@ private String getActorToken() { } private String getServiceAccountUserId() { - return agentApp.admin().getServiceAccountUser().getId(); + return agentApp.admin().toRepresentation().getServiceAccountUserId(); } private String getServiceAccountUserId(String clientUuid) { - return realm.admin().clients().get(clientUuid).getServiceAccountUser().getId(); + return realm.admin().clients().get(clientUuid).toRepresentation().getServiceAccountUserId(); } private String createClient(String clientId, String clientSecret) { diff --git a/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/StandardBaseTokenExchangeV2Test.java b/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/StandardBaseTokenExchangeV2Test.java index 0a4e9893d90f..6e9397c05eb1 100644 --- a/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/StandardBaseTokenExchangeV2Test.java +++ b/tests/base/src/test/java/org/keycloak/tests/oauth/tokenexchange/StandardBaseTokenExchangeV2Test.java @@ -327,7 +327,8 @@ public void testExchangeForIdToken() { @Test public void testExchangeUsingServiceAccount() { - final UserRepresentation user = realm.admin().clients().get(subjectClient.getId()).getServiceAccountUser(); + String saUserId = realm.admin().clients().get(subjectClient.getId()).toRepresentation().getServiceAccountUserId(); + final UserRepresentation user = realm.admin().users().get(saUserId).toRepresentation(); oauth.scope(null); oauth.client("subject-client", "secret"); diff --git a/tests/base/src/test/java/org/keycloak/tests/oid4vc/preauth/OID4VCredentialOfferPreAuthTest.java b/tests/base/src/test/java/org/keycloak/tests/oid4vc/preauth/OID4VCredentialOfferPreAuthTest.java index 217bf53280dc..d8a4c92c4d1d 100644 --- a/tests/base/src/test/java/org/keycloak/tests/oid4vc/preauth/OID4VCredentialOfferPreAuthTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/oid4vc/preauth/OID4VCredentialOfferPreAuthTest.java @@ -274,7 +274,8 @@ public void testPreAuthOffer_OriginatingUserPasswordChangeRevokesCode() { @Test public void testPreAuthOffer_ServiceAccountCanRedeemCode() { var ctx = new OID4VCTestContext(client, jwtTypeCredentialScope); - UserRepresentation serviceAccount = testRealm.admin().clients().get(client.getId()).getServiceAccountUser(); + String saUserId = testRealm.admin().clients().get(client.getId()).toRepresentation().getServiceAccountUserId(); + UserRepresentation serviceAccount = testRealm.admin().users().get(saUserId).toRepresentation(); UserResource serviceAccountResource = testRealm.admin().users().get(serviceAccount.getId()); RoleRepresentation credentialOfferRole = testRealm.admin().roles() .get(CREDENTIAL_OFFER_CREATE.getName()).toRepresentation(); diff --git a/tests/base/src/test/java/org/keycloak/tests/organization/mapper/OrganizationOIDCProtocolMapperTest.java b/tests/base/src/test/java/org/keycloak/tests/organization/mapper/OrganizationOIDCProtocolMapperTest.java index 46f1cce096c6..4593d8e05470 100644 --- a/tests/base/src/test/java/org/keycloak/tests/organization/mapper/OrganizationOIDCProtocolMapperTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/organization/mapper/OrganizationOIDCProtocolMapperTest.java @@ -2141,7 +2141,7 @@ public void testOrganizationClaimNotIssuedWhenOrganizationsDisabledClientCredent realm.admin().clients().get(createdClient.getId()).addOptionalClientScope(orgScope.getId()); // make the service account user a member of the organization - String serviceAccountUserId = realm.admin().clients().get(createdClient.getId()).getServiceAccountUser().getId(); + String serviceAccountUserId = realm.admin().clients().get(createdClient.getId()).toRepresentation().getServiceAccountUserId(); realm.admin().organizations().get(orgA.getId()).members().addMember(serviceAccountUserId).close(); // first, verify organization claim IS present when organizations are enabled diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupNamePolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupNamePolicyTest.java index 93ad2d7c0293..8ae455ffb998 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupNamePolicyTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/GroupNamePolicyTest.java @@ -152,12 +152,12 @@ public void testExactNameMatch() { } RealmResource realm = getRealm(); - UserRepresentation serviceAccount = getClient().getServiceAccountUser(); + String serviceAccountUserId = getClient().toRepresentation().getServiceAccountUserId(); GroupRepresentation rootGroup = realm.groups().groups().stream() .filter(group -> "Group A".equals(group.getName())) .findFirst() .orElseThrow(); - realm.users().get(serviceAccount.getId()).joinGroup(rootGroup.getId()); + realm.users().get(serviceAccountUserId).joinGroup(rootGroup.getId()); ticket = authzClient.protection().permission().create(request).getTicket(); response = authzClient.authorization(authzClient.obtainAccessToken().getToken()).authorize(new AuthorizationRequest(ticket)); assertNotNull(response.getToken()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RolePolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RolePolicyTest.java index f7d2020c2392..4d8eace604c2 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RolePolicyTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RolePolicyTest.java @@ -195,9 +195,9 @@ public void testFetchRolesUsingServiceAccount() { ClientScopeRepresentation rolesScope = AdminApiUtil.findClientScopeByName(realm, OIDCLoginProtocolFactory.ROLES_SCOPE).toRepresentation(); ClientResource clientResource = clients.get(client.getId()); clientResource.removeDefaultClientScope(rolesScope.getId()); - UserRepresentation serviceAccountUser = clientResource.getServiceAccountUser(); + String serviceAccountUserId = clientResource.toRepresentation().getServiceAccountUserId(); RoleRepresentation roleB = realm.roles().get("Role B").toRepresentation(); - realm.users().get(serviceAccountUser.getId()).roles().realmLevel().add(List.of(roleB)); + realm.users().get(serviceAccountUserId).roles().realmLevel().add(List.of(roleB)); RolePolicyRepresentation roleRep = clientResource.authorization().policies().role().findByName("Role B Policy"); roleRep.setFetchRoles(true); clientResource.authorization().policies().role().findById(roleRep.getId()).update(roleRep); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/admin/AuthorizationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/admin/AuthorizationTest.java index ba2991e7bb66..8d42c24bedc1 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/admin/AuthorizationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/admin/AuthorizationTest.java @@ -57,10 +57,10 @@ public void testEnableAuthorizationServices() { enableAuthorizationServices(false); enableAuthorizationServices(true); - serviceAccount = clientResource.getServiceAccountUser(); - Assertions.assertNotNull(serviceAccount); + String serviceAccountUserId = clientResource.toRepresentation().getServiceAccountUserId(); + Assertions.assertNotNull(serviceAccountUserId); realm = realmsResouce().realm(getRealmId()); - serviceAccountRoles = realm.users().get(serviceAccount.getId()).roles().clientLevel(resourceServer.getId()).listEffective(); + serviceAccountRoles = realm.users().get(serviceAccountUserId).roles().clientLevel(resourceServer.getId()).listEffective(); Assertions.assertTrue(serviceAccountRoles.stream().anyMatch(roleRepresentation -> "uma_protection".equals(roleRepresentation.getName()))); RolePolicyRepresentation policy = new RolePolicyRepresentation(); @@ -85,9 +85,9 @@ public void testEnableAuthorizationServices() { policies = clientResource.authorization().policies().policies(); assertTrue(policies.isEmpty()); - serviceAccount = clientResource.getServiceAccountUser(); - Assertions.assertNotNull(serviceAccount); - serviceAccountRoles = realm.users().get(serviceAccount.getId()).roles().clientLevel(resourceServer.getId()).listEffective(); + serviceAccountUserId = clientResource.toRepresentation().getServiceAccountUserId(); + Assertions.assertNotNull(serviceAccountUserId); + serviceAccountRoles = realm.users().get(serviceAccountUserId).roles().clientLevel(resourceServer.getId()).listEffective(); Assertions.assertTrue(serviceAccountRoles.stream().anyMatch(roleRepresentation -> "uma_protection".equals(roleRepresentation.getName()))); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/tokenexchange/StandardTokenExchangeV2Test.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/tokenexchange/StandardTokenExchangeV2Test.java index 3becbfc67377..55c8f9e4609c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/tokenexchange/StandardTokenExchangeV2Test.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/tokenexchange/StandardTokenExchangeV2Test.java @@ -583,7 +583,8 @@ public void testExchangeForIdToken() throws Exception { @Test @UncaughtServerErrorExpected public void testExchangeUsingServiceAccount() throws Exception { - final UserRepresentation user = AdminApiUtil.findClientResourceByClientId(adminClient.realm(TEST), "subject-client").getServiceAccountUser(); + final String saUserId = AdminApiUtil.findClientResourceByClientId(adminClient.realm(TEST), "subject-client").toRepresentation().getServiceAccountUserId(); + final UserRepresentation user = adminClient.realm(TEST).users().get(saUserId).toRepresentation(); oauth.realm(TEST); oauth.client("subject-client", "secret"); 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 bd498074b960..0fc8973155cf 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 @@ -153,8 +153,8 @@ public void clientConfiguration() { // Assign the role to TEST_CLIENT service account String testClientId = realm.clients().findByClientId(TEST_CLIENT).get(0).getId(); - UserRepresentation serviceAccount = realm.clients().get(testClientId).getServiceAccountUser(); - realm.users().get(serviceAccount.getId()).roles().clientLevel(resourceServerId).add(Collections.singletonList(role)); + String serviceAccountUserId = realm.clients().get(testClientId).toRepresentation().getServiceAccountUserId(); + realm.users().get(serviceAccountUserId).roles().clientLevel(resourceServerId).add(Collections.singletonList(role)); // Assign the role to test-user@localhost for user flows UserRepresentation testUser = realm.users().search("test-user@localhost").get(0); @@ -590,8 +590,8 @@ public void testAdminApiWithLightweightAccessTokenAndTransientSession() { 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())); + String serviceAccountUserId = masterRealm.clients().get(transientClient.getId()).toRepresentation().getServiceAccountUserId(); + masterRealm.users().get(serviceAccountUserId).roles().realmLevel().add(Collections.singletonList(masterRealm.roles().get(AdminRoles.ADMIN).toRepresentation())); try { oauth.realm("master"); oauth.client(transientClient.getClientId(), transientClient.getSecret()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientManager.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientManager.java index b875a0ded0f3..a348667e633d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientManager.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientManager.java @@ -204,7 +204,8 @@ public void setRequestUris(String... requestUris) { } public UserRepresentation getServiceAccountUser() { - return clientResource.getServiceAccountUser(); + String saUserId = clientResource.toRepresentation().getServiceAccountUserId(); + return realm.users().get(saUserId).toRepresentation(); } } }