1
0
mirror of https://github.com/keycloak/keycloak.git synced 2026-10-23 19:29:23 +03:00

Service account user ID available on client representation instead of exposing separate endpoint returning whole user representation

Closes #54047

Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
vramik
2026-10-19 19:42:52 +02:00
committed by Pedro Igor
parent 39e185405b
commit e203767c06
39 changed files with 258 additions and 163 deletions

View File

@@ -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 class ClientRepresentation {
this.serviceAccountsEnabled = serviceAccountsEnabled;
}
public String getServiceAccountUserId() {
return serviceAccountUserId;
}
public void setServiceAccountUserId(String serviceAccountUserId) {
this.serviceAccountUserId = serviceAccountUserId;
}
public Boolean getAuthorizationServicesEnabled() {
if (authorizationSettings != null) {
return true;

View File

@@ -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

View File

@@ -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

View File

@@ -38,7 +38,6 @@ import org.keycloak.representations.idm.ClientScopeRepresentation;
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)

View File

@@ -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<UserRepresentation>();
const [serviceAccountUserId, setServiceAccountUserId] = useState<string>();
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 ? (
<>
<PageSection className="pf-v5-u-pb-0">
<InfoCircleIcon className="pf-v5-c-alert__icon keycloak--service-account--info-text" />
@@ -75,16 +78,16 @@ export const ServiceAccount = ({ client }: ServiceAccountProps) => {
<Trans i18nKey="manageServiceAccountUser">
{""}
<Link
to={toUser({ realm, id: serviceAccount.id!, tab: "settings" })}
to={toUser({ realm, id: serviceAccountUserId, tab: "settings" })}
>
{{ link: serviceAccount.username }}
{{ link: serviceAccountUsername }}
</Link>
</Trans>
</span>
</PageSection>
<RoleMapping
name={client.clientId!}
id={serviceAccount.id!}
id={serviceAccountUserId}
type="users"
isManager={hasManageClients || client.access?.configure}
save={assignRoles}

View File

@@ -40,6 +40,7 @@ export default interface ClientRepresentation {
rootUrl?: string;
secret?: string;
serviceAccountsEnabled?: boolean;
serviceAccountUserId?: string;
standardFlowEnabled?: boolean;
surrogateAuthRequired?: boolean;
webOrigins?: string[];

View File

@@ -161,19 +161,6 @@ export class Clients extends Resource<{ realm?: string }> {
urlParamKeys: ["id", "roleName"],
});
/**
* Service account user
*/
public getServiceAccountUser = this.makeRequest<
{ id: string },
UserRepresentation
>({
method: "GET",
path: "/{id}/service-account-user",
urlParamKeys: ["id"],
});
/**
* Client secret
*/

View File

@@ -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;
});
});

View File

@@ -147,10 +147,10 @@ public class KcAdmV2KeystoreCLITest {
.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 {

View File

@@ -379,13 +379,13 @@ public class AdminGroupProtectionTest {
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<org.keycloak.scim.resource.group.Member> 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 class AdminGroupProtectionTest {
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 class AdminGroupProtectionTest {
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 class AdminGroupProtectionTest {
}
// 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<String> 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 class AdminGroupProtectionTest {
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(),

View File

@@ -309,10 +309,10 @@ public class AuthorizationTest extends AbstractScimTest {
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 class AuthorizationTest extends AbstractScimTest {
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 class AuthorizationTest extends AbstractScimTest {
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 class AuthorizationTest extends AbstractScimTest {
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 class AuthorizationTest extends AbstractScimTest {
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 class AuthorizationTest extends AbstractScimTest {
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 class AuthorizationTest extends AbstractScimTest {
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 class AuthorizationTest extends AbstractScimTest {
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 class AuthorizationTest extends AbstractScimTest {
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 class AuthorizationTest extends AbstractScimTest {
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 @@ public class AuthorizationTest extends AbstractScimTest {
}
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<String> resources, Set<String> scopes, String policyName) {
@@ -1526,7 +1527,8 @@ public class AuthorizationTest extends AbstractScimTest {
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 @@ public class AuthorizationTest extends AbstractScimTest {
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));
}
}

View File

@@ -19,7 +19,6 @@ import org.keycloak.representations.idm.ClientRepresentation;
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 class GroupTest extends AbstractScimTest {
}
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 class GroupTest extends AbstractScimTest {
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 class GroupTest extends AbstractScimTest {
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<String> groupIds = Optional.ofNullable(realm.admin().users().get(serviceAccount.getId()).groups()).orElse(List.of()).stream().map(GroupRepresentation::getId).toList();
List<String> 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");
}

View File

@@ -417,8 +417,8 @@ public class UserTest extends AbstractScimTest {
}
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 class UserTest extends AbstractScimTest {
}
realm.cleanup().add(realm -> realm.clients().get(targetClient.getId()).remove());
UserRepresentation serviceAccount = realm.admin().clients().get(targetClient.getId()).getServiceAccountUser();
ListResponse<User> response = client.users().getAll(ResourceFilter.filter().eq("id", serviceAccount.getId()).build());
String serviceAccountUserId = realm.admin().clients().get(targetClient.getId()).toRepresentation().getServiceAccountUserId();
ListResponse<User> response = client.users().getAll(ResourceFilter.filter().eq("id", serviceAccountUserId).build());
assertEquals(0, response.getTotalResults());
}
@@ -457,7 +457,8 @@ public class UserTest extends AbstractScimTest {
}
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 class UserTest extends AbstractScimTest {
}
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 class UserTest extends AbstractScimTest {
}
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");

View File

@@ -10,7 +10,6 @@ import org.keycloak.models.Constants;
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 class ScimClientSupplier implements Supplier<ScimClient, InjectScimClient
.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()

View File

@@ -1588,4 +1588,11 @@ public class ModelToRepresentation {
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());
}
}
}

View File

@@ -21,9 +21,13 @@ import java.util.ArrayList;
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 class PartialImportManager {
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);
}
}
}
}

View File

@@ -61,7 +61,6 @@ import org.keycloak.models.ModelDuplicateException;
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.ClientRepresentation;
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 class ClientResource {
representation.setAccess(auth.clients().getAccess(client));
if (client.isServiceAccountsEnabled()) {
ModelToRepresentation.setServiceAccountUserId(session, client, representation);
}
return representation;
}
@@ -522,26 +524,6 @@ public class ClientResource {
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
*

View File

@@ -135,6 +135,9 @@ public class ClientsResource {
StripSecretsUtils.stripClient(representation);
}
representation.setAccess(auth.clients().getAccess(c));
if (c.isServiceAccountsEnabled()) {
ModelToRepresentation.setServiceAccountUserId(session, c, representation);
}
return representation;
});
}

View File

@@ -24,7 +24,6 @@ import org.keycloak.representations.idm.ClientRepresentation;
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 @@ public class SsfTransmitterEventEmitterTests {
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) {

View File

@@ -844,7 +844,8 @@ public class ClientTest {
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());

View File

@@ -293,7 +293,8 @@ public class ImpersonationTest {
}
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 class ImpersonationTest {
}
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

View File

@@ -163,7 +163,6 @@ public class PermissionsTest extends AbstractPermissionsTest {
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);

View File

@@ -19,11 +19,18 @@ package org.keycloak.tests.admin.client;
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.ClientBuilder;
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 @@ import org.junit.jupiter.api.Test;
@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 class ServiceAccountClientTest {
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 class ServiceAccountClientTest {
.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;
}
}
}

View File

@@ -188,7 +188,7 @@ public class PartialImportClientTest extends AbstractPartialImportTest {
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

View File

@@ -56,7 +56,6 @@ import org.keycloak.representations.idm.ClientRepresentation;
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 class ClientRegistrationTest extends AbstractClientRegistrationTest {
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 class ClientRegistrationTest extends AbstractClientRegistrationTest {
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());

View File

@@ -76,7 +76,7 @@ public class OIDCClientRegistrationCorsTest {
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();

View File

@@ -83,7 +83,7 @@ public class SAMLClientRegistrationTest extends AbstractClientRegistrationTest {
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();

View File

@@ -554,7 +554,7 @@ public class ClientAgentDelegationTest {
}
private String getServiceAccountUserId() {
return actorApp.admin().getServiceAccountUser().getId();
return actorApp.admin().toRepresentation().getServiceAccountUserId();
}
private void logout(String refreshToken) {

View File

@@ -282,7 +282,7 @@ public class ClientDelegationCibaTest {
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"));
}

View File

@@ -534,11 +534,11 @@ public class ClientDelegationTest {
}
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) {

View File

@@ -327,7 +327,8 @@ public class StandardBaseTokenExchangeV2Test extends AbstractBaseTokenExchangeTe
@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");

View File

@@ -274,7 +274,8 @@ public class OID4VCredentialOfferPreAuthTest extends OID4VCIssuerTestBase {
@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();

View File

@@ -2141,7 +2141,7 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
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

View File

@@ -152,12 +152,12 @@ public class GroupNamePolicyTest extends AbstractAuthzTest {
}
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());

View File

@@ -195,9 +195,9 @@ public class RolePolicyTest extends AbstractAuthzTest {
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);

View File

@@ -57,10 +57,10 @@ public class AuthorizationTest extends AbstractAuthorizationTest {
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 class AuthorizationTest extends AbstractAuthorizationTest {
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())));
}
}

View File

@@ -583,7 +583,8 @@ public abstract class StandardTokenExchangeV2Test extends AbstractClientPolicies
@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");

View File

@@ -153,8 +153,8 @@ public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
// 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 class LightWeightAccessTokenTest extends AbstractClientPoliciesTest {
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());

View File

@@ -204,7 +204,8 @@ public class ClientManager {
}
public UserRepresentation getServiceAccountUser() {
return clientResource.getServiceAccountUser();
String saUserId = clientResource.toRepresentation().getServiceAccountUserId();
return realm.users().get(saUserId).toRepresentation();
}
}
}