[FGAP] Add adminPermissionClientCheck to authorization services REST endpoints

Closes #35945

Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
vramik 2024-12-16 14:13:14 +01:00 committed by Pedro Igor
parent 6af76d895e
commit 0a632fdefa
22 changed files with 582 additions and 177 deletions

View File

@ -18,21 +18,52 @@ package org.keycloak.representations.idm.authorization;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class AuthorizationSchema {
private final Set<ResourceType> resourceTypes;
@JsonDeserialize(using = ResourceTypeDeserializer.class)
private final Map<String, ResourceType> resourceTypes;
@JsonCreator
public AuthorizationSchema(@JsonProperty("resourceTypes") ResourceType... resourceTypes) {
this.resourceTypes = Arrays.stream(resourceTypes).collect(Collectors.toSet());
public AuthorizationSchema(@JsonProperty("resourceTypes") Map<String, ResourceType> resourceTypes) {
this.resourceTypes = resourceTypes;
}
public Set<ResourceType> getResourceTypes() {
return Collections.unmodifiableSet(resourceTypes);
public Map<String, ResourceType> getResourceTypes() {
return Collections.unmodifiableMap(resourceTypes);
}
// Custom deserializer to handle both arrays and maps
public static class ResourceTypeDeserializer extends JsonDeserializer<Map<String, ResourceType>> {
@Override
public Map<String, ResourceType> deserialize(JsonParser parser, DeserializationContext context) throws IOException {
// Check if the input is an array or an object
if (parser.isExpectedStartArrayToken()) {
// Deserialize array of ResourceType and convert to Map
List<ResourceType> resourceTypeList = parser.readValueAs(new TypeReference<List<ResourceType>>() {});
return resourceTypeList.stream()
.collect(Collectors.toMap(ResourceType::getType, Function.identity()));
} else if (parser.isExpectedStartObjectToken()) {
// Deserialize directly as a Map
return parser.readValueAs(new TypeReference<Map<String, ResourceType>>() {});
} else {
// Throw JsonMappingException for unexpected formats
throw JsonMappingException.from(parser, "Expected an array or object for resourceTypes");
}
}
}
}

View File

@ -52,6 +52,11 @@ public interface ResourcesResource {
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult);
@GET
@Path("/search")
@Produces(MediaType.APPLICATION_JSON)
ResourceRepresentation searchByName(@QueryParam("name") String name);
@GET
@Produces(MediaType.APPLICATION_JSON)
List<ResourceRepresentation> findByName(@QueryParam("name") String name);

View File

@ -16,26 +16,31 @@
*/
package org.keycloak.authorization;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelValidationException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.representations.idm.authorization.AuthorizationSchema;
import org.keycloak.representations.idm.authorization.ResourceType;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
public class AdminPermissionsAuthorizationSchema extends AuthorizationSchema {
public class AdminPermissionsSchema extends AuthorizationSchema {
public static final ResourceType USERS = new ResourceType("Users", new HashSet<>(Arrays.asList("manage")));
public static final AdminPermissionsAuthorizationSchema INSTANCE = new AdminPermissionsAuthorizationSchema();
public static final String USERS_RESOURCE_TYPE = "Users";
public static final ResourceType USERS = new ResourceType(USERS_RESOURCE_TYPE, Set.of("manage"));
public static final AdminPermissionsSchema SCHEMA = new AdminPermissionsSchema();
private AdminPermissionsAuthorizationSchema() {
super(USERS);
private AdminPermissionsSchema() {
super(Map.of(USERS_RESOURCE_TYPE, USERS));
}
public Resource getOrCreateResource(KeycloakSession session, ResourceServer resourceServer, String type, String id) {
@ -71,13 +76,17 @@ public class AdminPermissionsAuthorizationSchema extends AuthorizationSchema {
return false;
}
ClientModel permissionClient = realm.getAdminPermissionsClient();
return isAdminPermissionClient(realm, resourceServer.getId());
}
if (permissionClient == null) {
throw new IllegalStateException("Permission client not found");
private boolean isAdminPermissionClient(RealmModel realm, String id) {
return realm.getAdminPermissionsClient() != null && realm.getAdminPermissionsClient().getId().equals(id);
}
public void throwExceptionIfAdminPermissionClient(KeycloakSession session, String id) {
if (isAdminPermissionClient(session.getContext().getRealm(), id)) {
throw new ModelValidationException("Not supported for this client.");
}
return resourceServer.getId().equals(permissionClient.getId());
}
private Resource getOrCreateResource(KeycloakSession session, ResourceServer resourceServer, String id) {
@ -106,4 +115,35 @@ public class AdminPermissionsAuthorizationSchema extends AuthorizationSchema {
AuthorizationProvider authzProvider = session.getProvider(AuthorizationProvider.class);
return authzProvider.getStoreFactory();
}
public void throwExceptionIfResourceTypeOrScopesNotProvided(KeycloakSession session, ResourceServer resourceServer, AbstractPolicyRepresentation rep) {
if (!supportsAuthorizationSchema(session, resourceServer)) {
return;
}
if (rep instanceof ScopePermissionRepresentation) {
if (rep.getResourceType() == null || SCHEMA.getResourceTypes().get(rep.getResourceType()) == null) {
throw new ModelValidationException("Resource type not provided.");
}
if (rep.getScopes() == null || rep.getScopes().isEmpty()) {
throw new ModelValidationException("Scopes not provided.");
}
}
}
public Scope getScope(KeycloakSession session, ResourceServer resourceServer, String resourceType, String id) {
StoreFactory storeFactory = getStoreFactory(session);
Scope scope = Optional.ofNullable(storeFactory.getScopeStore().findById(resourceServer, id))
.or(() -> Optional.ofNullable(storeFactory.getScopeStore().findByName(resourceServer, id)))
.orElseThrow(() -> new ModelValidationException(String.format("Scope [%s] does not exist.", id)));
if (supportsAuthorizationSchema(session, resourceServer)) {
//validations for schema
if (!SCHEMA.getResourceTypes().get(resourceType).getScopes().contains(scope.getName())) {
throw new ModelValidationException(String.format("Scope %s was not found for resource type %s.", scope.getName(), resourceType));
}
}
return scope;
}
}

View File

@ -42,11 +42,14 @@ import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelValidationException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.provider.Provider;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.representations.idm.authorization.AuthorizationSchema;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
/**
* <p>The main contract here is the creation of {@link org.keycloak.authorization.permission.evaluator.PermissionEvaluator} instances. Usually
@ -293,11 +296,12 @@ public final class AuthorizationProvider implements Provider {
@Override
public Policy create(ResourceServer resourceServer, AbstractPolicyRepresentation representation) {
AdminPermissionsSchema.SCHEMA.throwExceptionIfResourceTypeOrScopesNotProvided(keycloakSession, resourceServer, representation);
Set<String> resources = representation.getResources();
if (resources != null) {
representation.setResources(resources.stream().map(id -> {
Resource resource = AdminPermissionsAuthorizationSchema.INSTANCE.getOrCreateResource(keycloakSession, resourceServer, representation.getResourceType(), id);
Resource resource = AdminPermissionsSchema.SCHEMA.getOrCreateResource(keycloakSession, resourceServer, representation.getResourceType(), id);
if (resource == null) {
resource = storeFactory.getResourceStore().findById(resourceServer, id);
@ -320,22 +324,11 @@ public final class AuthorizationProvider implements Provider {
Set<String> scopes = representation.getScopes();
if (scopes != null) {
representation.setScopes(scopes.stream().map(id -> {
Scope scope = storeFactory.getScopeStore().findById(resourceServer, id);
if (scope == null) {
scope = storeFactory.getScopeStore().findByName(resourceServer, id);
}
if (scope == null) {
throw new RuntimeException("Scope [" + id + "] does not exist");
}
return scope.getId();
}).collect(Collectors.toSet()));
representation.setScopes(scopes.stream()
.map(id -> AdminPermissionsSchema.SCHEMA.getScope(keycloakSession, resourceServer, representation.getResourceType(), id).getId())
.collect(Collectors.toSet()));
}
Set<String> policies = representation.getPolicies();
if (policies != null) {

View File

@ -20,7 +20,7 @@ package org.keycloak.models.utils;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.Config.Scope;
import org.keycloak.authorization.AdminPermissionsAuthorizationSchema;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.broker.social.SocialIdentityProviderFactory;
import org.keycloak.common.util.CertificateUtils;
@ -89,7 +89,7 @@ import java.util.stream.Stream;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.common.Profile;
import org.keycloak.representations.idm.authorization.AuthorizationSchema;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
@ -1202,16 +1202,20 @@ public final class KeycloakModelUtils {
ResourceServer resourceServer = RepresentationToModel.createResourceServer(client, session, false);
ResourceServerRepresentation resourceServerRep = ModelToRepresentation.toRepresentation(resourceServer, client);
AuthorizationSchema schema = AdminPermissionsAuthorizationSchema.INSTANCE;
//create all scopes defined in the schema
//there is no way how to map scopes to the resourceType, we need to collect all scopes from all resourceTypes
Set<ScopeRepresentation> scopes = schema.getResourceTypes().stream()
Set<ScopeRepresentation> scopes = AdminPermissionsSchema.SCHEMA.getResourceTypes().values().stream()
.flatMap((resourceType) -> resourceType.getScopes().stream())
.map(scope -> new ScopeRepresentation(scope))
.collect(Collectors.toSet());//collecting to set to get rid of duplicities
resourceServerRep.setScopes(List.copyOf(scopes));
//create 'all-resource' resources defined in the schema
resourceServerRep.setResources(AdminPermissionsSchema.SCHEMA.getResourceTypes().keySet().stream()
.map(type -> new ResourceRepresentation(type))
.collect(Collectors.toList()));
RepresentationToModel.toModel(resourceServerRep, session.getProvider(AuthorizationProvider.class), client);
}

View File

@ -23,7 +23,7 @@ import static org.keycloak.models.utils.StripSecretsUtils.stripSecrets;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.authentication.otp.OTPApplicationProvider;
import org.keycloak.authorization.AdminPermissionsAuthorizationSchema;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.AuthorizationProviderFactory;
import org.keycloak.authorization.model.PermissionTicket;
@ -1068,7 +1068,7 @@ public class ModelToRepresentation {
if (adminPermissionsClient == null || ! client.getClientId().equals(adminPermissionsClient.getClientId())) {
return null;
}
return AdminPermissionsAuthorizationSchema.INSTANCE;
return AdminPermissionsSchema.SCHEMA;
}
public static <R extends AbstractPolicyRepresentation> R toRepresentation(Policy policy, AuthorizationProvider authorization) {

View File

@ -27,7 +27,6 @@ import java.util.stream.Collectors;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
@ -44,7 +43,7 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.jboss.resteasy.reactive.NoCache;
import org.keycloak.authorization.AdminPermissionsAuthorizationSchema;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
@ -358,7 +357,7 @@ public class PolicyService {
}
private void checkIfSupportedPolicyType(String type) throws BadRequestException {
if (AdminPermissionsAuthorizationSchema.INSTANCE.isSupportedPolicyType(authorization.getKeycloakSession(), resourceServer, type)) {
if (AdminPermissionsSchema.SCHEMA.isSupportedPolicyType(authorization.getKeycloakSession(), resourceServer, type)) {
return;
}

View File

@ -19,6 +19,7 @@ package org.keycloak.authorization.admin;
import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
import java.util.Collections;
import java.util.HashMap;
import jakarta.ws.rs.Consumes;
@ -33,6 +34,7 @@ import jakarta.ws.rs.core.UriInfo;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.events.admin.OperationType;
@ -52,8 +54,6 @@ import org.keycloak.services.resources.KeycloakOpenAPI;
import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import java.util.Collections;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@ -77,6 +77,8 @@ public class ResourceServerService {
}
public ResourceServer create(boolean newClient) {
AdminPermissionsSchema.SCHEMA.throwExceptionIfAdminPermissionClient(session, client.getId());
this.auth.realm().requireManageAuthorization();
UserModel serviceAccount = this.session.users().getServiceAccount(client);
@ -99,6 +101,8 @@ public class ResourceServerService {
@Produces(MediaType.APPLICATION_JSON)
@APIResponse(responseCode = "204", description = "No Content")
public Response update(ResourceServerRepresentation server) {
AdminPermissionsSchema.SCHEMA.throwExceptionIfAdminPermissionClient(session, client.getId());
this.auth.realm().requireManageAuthorization();
this.resourceServer.setAllowRemoteResourceManagement(server.isAllowRemoteResourceManagement());
this.resourceServer.setPolicyEnforcementMode(server.getPolicyEnforcementMode());
@ -108,6 +112,8 @@ public class ResourceServerService {
}
public void delete() {
AdminPermissionsSchema.SCHEMA.throwExceptionIfAdminPermissionClient(session, client.getId());
this.auth.realm().requireManageAuthorization();
//need to create representation before the object is deleted to be able to get lazy loaded fields
ResourceServerRepresentation rep = ModelToRepresentation.toRepresentation(resourceServer, client);
@ -126,6 +132,7 @@ public class ResourceServerService {
@GET
@Produces(MediaType.APPLICATION_JSON)
public ResourceServerRepresentation exportSettings() {
AdminPermissionsSchema.SCHEMA.throwExceptionIfAdminPermissionClient(session, client.getId());
this.auth.realm().requireManageAuthorization();
return ModelToRepresentation.toResourceServerRepresentation(session, client);
}
@ -135,6 +142,7 @@ public class ResourceServerService {
@Consumes(MediaType.APPLICATION_JSON)
@APIResponse(responseCode = "204", description = "No Content")
public Response importSettings(ResourceServerRepresentation rep) {
AdminPermissionsSchema.SCHEMA.throwExceptionIfAdminPermissionClient(session, client.getId());
this.auth.realm().requireManageAuthorization();
rep.setClientId(client.getId());

View File

@ -53,6 +53,7 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.jboss.resteasy.reactive.NoCache;
import org.keycloak.OAuthErrorException;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
@ -87,8 +88,8 @@ public class ResourceSetService {
private final AuthorizationProvider authorization;
private final AdminPermissionEvaluator auth;
private final AdminEventBuilder adminEvent;
private KeycloakSession session;
private ResourceServer resourceServer;
private final KeycloakSession session;
private final ResourceServer resourceServer;
public ResourceSetService(KeycloakSession session, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
this.session = session;
@ -122,6 +123,9 @@ public class ResourceSetService {
}
public ResourceRepresentation create(ResourceRepresentation resource) {
// direct creation of resources it's not expected for admin permission
AdminPermissionsSchema.SCHEMA.throwExceptionIfAdminPermissionClient(session, resourceServer.getId());
requireManage();
StoreFactory storeFactory = this.authorization.getStoreFactory();
ResourceOwnerRepresentation owner = resource.getOwner();
@ -156,6 +160,7 @@ public class ResourceSetService {
@APIResponse(responseCode = "404", description = "Not Found")
})
public Response update(@PathParam("resource-id") String id, ResourceRepresentation resource) {
AdminPermissionsSchema.SCHEMA.throwExceptionIfAdminPermissionClient(session, resourceServer.getId());
requireManage();
resource.setId(id);
StoreFactory storeFactory = this.authorization.getStoreFactory();
@ -180,6 +185,7 @@ public class ResourceSetService {
@APIResponse(responseCode = "404", description = "Not Found")
})
public Response delete(@PathParam("resource-id") String id) {
AdminPermissionsSchema.SCHEMA.throwExceptionIfAdminPermissionClient(session, resourceServer.getId());
requireManage();
StoreFactory storeFactory = authorization.getStoreFactory();
Resource resource = storeFactory.getResourceStore().findById(resourceServer, id);

View File

@ -23,6 +23,7 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.jboss.resteasy.reactive.NoCache;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
@ -34,7 +35,6 @@ import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
@ -75,8 +75,8 @@ public class ScopeService {
private final AuthorizationProvider authorization;
private final AdminPermissionEvaluator auth;
private final AdminEventBuilder adminEvent;
private KeycloakSession session;
private ResourceServer resourceServer;
private final KeycloakSession session;
private final ResourceServer resourceServer;
public ScopeService(KeycloakSession session, ResourceServer resourceServer, AuthorizationProvider authorization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
this.session = session;
@ -91,7 +91,8 @@ public class ScopeService {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response create(ScopeRepresentation scope) {
this.auth.realm().requireManageAuthorization();
AdminPermissionsSchema.SCHEMA.throwExceptionIfAdminPermissionClient(session, resourceServer.getId());
this.auth.realm().requireManageAuthorization();
Scope model = toModel(scope, this.resourceServer, authorization);
scope.setId(model.getId());
@ -106,7 +107,8 @@ public class ScopeService {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response update(@PathParam("scope-id") String id, ScopeRepresentation scope) {
this.auth.realm().requireManageAuthorization();
AdminPermissionsSchema.SCHEMA.throwExceptionIfAdminPermissionClient(session, resourceServer.getId());
this.auth.realm().requireManageAuthorization();
scope.setId(id);
StoreFactory storeFactory = authorization.getStoreFactory();
Scope model = storeFactory.getScopeStore().findById(resourceServer, scope.getId());
@ -125,6 +127,7 @@ public class ScopeService {
@Path("{scope-id}")
@DELETE
public Response delete(@PathParam("scope-id") String id) {
AdminPermissionsSchema.SCHEMA.throwExceptionIfAdminPermissionClient(session, resourceServer.getId());
this.auth.realm().requireManageAuthorization();
StoreFactory storeFactory = authorization.getStoreFactory();
Scope scope = storeFactory.getScopeStore().findById(resourceServer, id);

View File

@ -16,8 +16,6 @@
*/
package org.keycloak.services.resources.admin.permissions;
import org.keycloak.models.RealmModel;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $

View File

@ -50,6 +50,11 @@ public class ClientConfigBuilder {
return this;
}
public ClientConfigBuilder authorizationServices() {
rep.setAuthorizationServicesEnabled(true);
return this;
}
public ClientRepresentation build() {
return rep;
}

View File

@ -17,10 +17,16 @@
package org.keycloak.test.admin.authz.fgap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import jakarta.ws.rs.core.Response;
import java.util.Set;
import org.keycloak.admin.client.resource.PermissionsResource;
import org.keycloak.admin.client.resource.PoliciesResource;
import org.keycloak.admin.client.resource.ScopePermissionsResource;
import org.keycloak.models.Constants;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.test.framework.annotations.InjectClient;
import org.keycloak.test.framework.annotations.InjectRealm;
import org.keycloak.test.framework.realm.ManagedClient;
@ -45,4 +51,43 @@ public abstract class AbstractPermissionTest {
protected ScopePermissionsResource getScopePermissionsResource() {
return getPermissionsResource().scope();
}
protected void createPermission(ScopePermissionRepresentation permission) {
this.createPermission(permission, Response.Status.CREATED);
}
protected void createPermission(ScopePermissionRepresentation permission, Response.Status expected) {
try (Response response = getScopePermissionsResource().create(permission)) {
assertEquals(expected.getStatusCode(), response.getStatus());
}
}
protected static class PermissionBuilder {
private final ScopePermissionRepresentation permission;
static PermissionBuilder create() {
ScopePermissionRepresentation rep = new ScopePermissionRepresentation();
rep.setName(KeycloakModelUtils.generateId());
return new PermissionBuilder(rep);
}
private PermissionBuilder(ScopePermissionRepresentation rep) {
this.permission = rep;
}
ScopePermissionRepresentation build() {
return permission;
}
PermissionBuilder resourceType(String resourceType) {
permission.setResourceType(resourceType);
return this;
}
PermissionBuilder scopes(Set<String> scopes) {
permission.setScopes(scopes);
return this;
}
PermissionBuilder resources(Set<String> resources) {
permission.setResources(resources);
return this;
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2025 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.test.admin.authz.fgap;
import org.keycloak.test.framework.realm.ClientConfig;
import org.keycloak.test.framework.realm.ClientConfigBuilder;
public class AuthzClientConfig implements ClientConfig {
@Override
public ClientConfigBuilder configure(ClientConfigBuilder client) {
return client.serviceAccount()
.authorizationServices();
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2025 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.test.admin.authz.fgap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import org.junit.jupiter.api.Test;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.test.framework.annotations.InjectClient;
import org.keycloak.test.framework.annotations.KeycloakIntegrationTest;
import org.keycloak.test.framework.realm.ManagedClient;
@KeycloakIntegrationTest
public class FeatureDisabledTest {
@InjectClient(ref = "test-client", config = AuthzClientConfig.class, createClient = true)
private ManagedClient testClient;
@Test
public void schemaNotAvailableFeatureDisabled() {
ResourceServerRepresentation authorizationSettings = testClient.admin().authorization().getSettings();
assertThat(authorizationSettings, notNullValue());
assertThat(authorizationSettings.getAuthorizationSchema(), nullValue());
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2025 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.test.admin.authz.fgap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import org.junit.jupiter.api.Test;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.test.framework.annotations.InjectClient;
import org.keycloak.test.framework.annotations.KeycloakIntegrationTest;
import org.keycloak.test.framework.realm.ManagedClient;
@KeycloakIntegrationTest(config = KeycloakAdminPermissionsV1ServerConfig.class)
public class FeatureV1EnabledTest {
@InjectClient(ref = "test-client", config = AuthzClientConfig.class, createClient = true)
private ManagedClient testClient;
@Test
public void schemaNotAvailableFeatureV1Enabled() {
ResourceServerRepresentation authorizationSettings = testClient.admin().authorization().getSettings();
assertThat(authorizationSettings, notNullValue());
assertThat(authorizationSettings.getAuthorizationSchema(), nullValue());
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2025 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.test.admin.authz.fgap;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import org.junit.jupiter.api.Test;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.test.framework.annotations.InjectClient;
import org.keycloak.test.framework.annotations.InjectRealm;
import org.keycloak.test.framework.annotations.KeycloakIntegrationTest;
import org.keycloak.test.framework.realm.ManagedClient;
import org.keycloak.test.framework.realm.ManagedRealm;
@KeycloakIntegrationTest(config = KeycloakAdminPermissionsServerConfig.class)
public class FeatureV2EnabledTest {
@InjectRealm
private ManagedRealm realm;
@InjectClient(ref = "test-client", config = AuthzClientConfig.class, createClient = true)
private ManagedClient testClient;
@Test
public void schemaNotAvailableForNonAdminPermissionClient() {
ResourceServerRepresentation authorizationSettings = testClient.admin().authorization().getSettings();
assertThat(authorizationSettings, notNullValue());
assertThat(authorizationSettings.getAuthorizationSchema(), nullValue());
}
@Test
public void schemaAvailableAfterFGAPEnabledForRealm() {
// admin permissions client should not exist when the switch in not enabled for the realm
assertThat(realm.admin().clients().findByClientId(Constants.ADMIN_PERMISSIONS_CLIENT_ID), is(empty()));
// enable admin permissions for the realm
RealmRepresentation realmRep = realm.admin().toRepresentation();
realmRep.setAdminPermissionsEnabled(Boolean.TRUE);
realm.admin().update(realmRep);
List<ClientRepresentation> clients = realm.admin().clients().findByClientId(Constants.ADMIN_PERMISSIONS_CLIENT_ID);
assertThat(clients, hasSize(1));
ResourceServerRepresentation authorizationSettings = realm.admin().clients().get(clients.get(0).getId()).authorization().getSettings();
assertThat(authorizationSettings, notNullValue());
assertThat(authorizationSettings.getAuthorizationSchema(), notNullValue());
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.test.admin.authz.fgap;
import org.keycloak.common.Profile.Feature;
import org.keycloak.test.framework.server.KeycloakServerConfig;
import org.keycloak.test.framework.server.KeycloakServerConfigBuilder;
public class KeycloakAdminPermissionsV1ServerConfig implements KeycloakServerConfig {
@Override
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
return config.features(Feature.ADMIN_FINE_GRAINED_AUTHZ);
}
}

View File

@ -25,6 +25,7 @@ import java.util.function.Supplier;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import org.junit.jupiter.api.Test;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.representations.idm.authorization.AggregatePolicyRepresentation;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
import org.keycloak.representations.idm.authorization.ClientScopePolicyRepresentation;
@ -34,7 +35,6 @@ import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.RegexPolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.TimePolicyRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.test.framework.annotations.KeycloakIntegrationTest;
@ -49,7 +49,10 @@ public class PermissionClientTest extends AbstractPermissionTest {
@Test
public void testSupportedPolicyTypes() {
assertSupportForPolicyType("scope", () -> getPermissionsResource().scope().create(new ScopePermissionRepresentation()), true);
assertSupportForPolicyType("scope", () -> getPermissionsResource().scope().create(PermissionBuilder.create()
.resourceType(AdminPermissionsSchema.USERS.getType())
.scopes(AdminPermissionsSchema.USERS.getScopes())
.build()), true);
assertSupportForPolicyType("user", () -> getPolicies().user().create(new UserPolicyRepresentation()), true);
assertSupportForPolicyType("client", () -> getPolicies().client().create(new ClientPolicyRepresentation()), true);
assertSupportForPolicyType("group", () -> getPolicies().group().create(new GroupPolicyRepresentation()), true);

View File

@ -0,0 +1,165 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.test.admin.authz.fgap;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.core.Response;
import java.util.Set;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
import org.junit.jupiter.api.Test;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.test.framework.annotations.InjectUser;
import org.keycloak.test.framework.annotations.KeycloakIntegrationTest;
import org.keycloak.test.framework.realm.ManagedUser;
@KeycloakIntegrationTest(config = KeycloakAdminPermissionsServerConfig.class)
public class PermissionRESTTest extends AbstractPermissionTest {
@InjectUser(ref = "alice")
private ManagedUser userAlice;
@Test
public void resourceServerTest() {
ResourceServerRepresentation rep = new ResourceServerRepresentation();
rep.setPolicyEnforcementMode(PolicyEnforcementMode.DISABLED);
rep.setDecisionStrategy(DecisionStrategy.CONSENSUS);
try {
client.admin().authorization().update(rep);
} catch (Exception ex) {
assertThat(ex, instanceOf(BadRequestException.class));
}
try {
client.admin().authorization().exportSettings();
} catch (Exception ex) {
assertThat(ex, instanceOf(BadRequestException.class));
}
try {
client.admin().authorization().importSettings(rep);
} catch (Exception ex) {
assertThat(ex, instanceOf(BadRequestException.class));
}
}
@Test
public void scopesTest() {
ScopeRepresentation manage = client.admin().authorization().scopes().findByName("manage");
assertThat(manage, notNullValue());
ScopeRepresentation customScope = new ScopeRepresentation();
customScope.setName("custom");
try (Response response = client.admin().authorization().scopes().create(customScope)) {
assertThat(response.getStatus(), equalTo(Response.Status.BAD_REQUEST.getStatusCode()));
}
try {
client.admin().authorization().scopes().scope(manage.getId()).update(manage);
} catch (Exception ex) {
assertThat(ex, instanceOf(BadRequestException.class));
}
try {
client.admin().authorization().scopes().scope(manage.getId()).remove();
} catch (Exception ex) {
assertThat(ex, instanceOf(BadRequestException.class));
}
}
@Test
public void resourcesTest() {
ResourceRepresentation resourceRep = new ResourceRepresentation("resource-1", "manage");
resourceRep.setType(AdminPermissionsSchema.USERS.getType());
//it is not expected to create resources directly
try (Response response = client.admin().authorization().resources().create(resourceRep)) {
assertThat(response.getStatus(), equalTo(Response.Status.BAD_REQUEST.getStatusCode()));
}
ResourceRepresentation usersResource = client.admin().authorization().resources().searchByName(AdminPermissionsSchema.USERS.getType());
assertThat(usersResource, notNullValue());
// updates to 'all resource type' resources not expected
try {
client.admin().authorization().resources().resource(usersResource.getId()).update(resourceRep);
} catch (Exception ex) {
assertThat(ex, instanceOf(BadRequestException.class));
}
// deletes to 'all resource type' resources not expected
try {
client.admin().authorization().resources().resource(usersResource.getId()).remove();
} catch (Exception ex) {
assertThat(ex, instanceOf(BadRequestException.class));
}
// this should create a resource for userAlice
createPermission(PermissionBuilder.create()
.resourceType(AdminPermissionsSchema.USERS.getType())
.resources(Set.of(userAlice.getUsername()))
.scopes(AdminPermissionsSchema.USERS.getScopes())
.build());
// resourceName should equal to userAlice.getId() by design
ResourceRepresentation userAliceResourceRep = client.admin().authorization().resources().searchByName(userAlice.getId());
assertThat(userAliceResourceRep, notNullValue());
String aliceResourceId = userAliceResourceRep.getId();
// updates not expected
try {
client.admin().authorization().resources().resource(aliceResourceId).update(userAliceResourceRep);
} catch (Exception ex) {
assertThat(ex, instanceOf(BadRequestException.class));
}
// delete not expected
try {
client.admin().authorization().resources().resource(aliceResourceId).remove();
} catch (Exception ex) {
assertThat(ex, instanceOf(BadRequestException.class));
}
}
@Test
public void permissionsTest() {
// no resourceType, valid scopes
createPermission(PermissionBuilder.create()
.scopes(AdminPermissionsSchema.USERS.getScopes())
.build(), Response.Status.BAD_REQUEST);
// valid resourceType, no scopes
createPermission(PermissionBuilder.create()
.resourceType(AdminPermissionsSchema.USERS.getType())
.build(), Response.Status.BAD_REQUEST);
// valid resourceType, non-existent scopes
createPermission(PermissionBuilder.create()
.resourceType(AdminPermissionsSchema.USERS.getType())
.scopes(Set.of("edit", "write", "token-exchange"))
.build(), Response.Status.BAD_REQUEST);
}
}

View File

@ -19,16 +19,16 @@ package org.keycloak.test.admin.authz.fgap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import jakarta.ws.rs.core.Response;
import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.ScopePermissionResource;
import org.keycloak.admin.client.resource.ScopePermissionsResource;
import org.keycloak.authorization.AdminPermissionsAuthorizationSchema;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
@ -92,8 +92,7 @@ public class UserResourceTypePermissionTest extends AbstractPermissionTest {
@Test
public void testFindByResourceObject() {
createUserPermission(userAlice);
createUserPermission(userBob);
createUserPermission(userAlice, userBob);
List<ScopePermissionRepresentation> existing = getScopePermissionsResource().findAll(null, null, userAlice.getId(), -1, -1);
assertEquals(1, existing.size());
@ -116,13 +115,13 @@ public class UserResourceTypePermissionTest extends AbstractPermissionTest {
assertEquals(1, existing.size());
}
private ScopePermissionRepresentation createUserPermission(ManagedUser user) {
private ScopePermissionRepresentation createUserPermission(ManagedUser... users) {
ScopePermissionRepresentation permission = new ScopePermissionRepresentation();
permission.setName(KeycloakModelUtils.generateId());
permission.setResourceType(AdminPermissionsAuthorizationSchema.USERS.getType());
permission.setResources(Set.of(user.getUsername()));
permission.setScopes(AdminPermissionsAuthorizationSchema.USERS.getScopes());
permission.setResourceType(AdminPermissionsSchema.USERS.getType());
permission.setResources(Arrays.stream(users).map(ManagedUser::getUsername).collect(Collectors.toSet()));
permission.setScopes(AdminPermissionsSchema.USERS.getScopes());
permission.setPolicies(Set.of("User Policy 0", "User Policy 1", "User Policy 2"));
createPermission(permission);
@ -134,19 +133,12 @@ public class UserResourceTypePermissionTest extends AbstractPermissionTest {
ScopePermissionRepresentation permission = new ScopePermissionRepresentation();
permission.setName(KeycloakModelUtils.generateId());
permission.setResourceType(AdminPermissionsAuthorizationSchema.USERS.getType());
permission.setScopes(AdminPermissionsAuthorizationSchema.USERS.getScopes());
permission.setResourceType(AdminPermissionsSchema.USERS.getType());
permission.setScopes(AdminPermissionsSchema.USERS.getScopes());
permission.setPolicies(Set.of("User Policy 0", "User Policy 1", "User Policy 2"));
createPermission(permission);
return permission;
}
private void createPermission(ScopePermissionRepresentation permission) {
try (Response response = getScopePermissionsResource().create(permission)) {
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
}
}
}

View File

@ -1,101 +0,0 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.authz.admin.permissions;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import java.util.List;
import java.util.stream.Collectors;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.keycloak.common.Profile;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
import org.keycloak.testsuite.util.ClientBuilder;
public class AdminPermissionsTest extends AbstractTestRealmKeycloakTest {
private final String CLIENT_ID = "fgap-client";
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
testRealm.getClients().add(ClientBuilder.create()
.clientId(CLIENT_ID)
.serviceAccount()
.authorizationServicesEnabled(true)
.build());
}
@Test
public void authorizationSchemaNotAvailableFeatureDisabled() {
List<ClientRepresentation> clients = testRealm().clients().findByClientId(CLIENT_ID);
assertThat(clients, hasSize(1));
ResourceServerRepresentation authorizationSettings = testRealm().clients().get(clients.get(0).getId()).authorization().getSettings();
assertThat(authorizationSettings, notNullValue());
assertThat(authorizationSettings.getAuthorizationSchema(), nullValue());
}
@Test
@EnableFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ)
public void authorizationSchemaNotAvailableFeatureV1Enabled() throws Exception {
reconnectAdminClient();
List<ClientRepresentation> clients = testRealm().clients().findByClientId(CLIENT_ID);
assertThat(clients, hasSize(1));
ResourceServerRepresentation authorizationSettings = testRealm().clients().get(clients.get(0).getId()).authorization().getSettings();
assertThat(authorizationSettings, notNullValue());
assertThat(authorizationSettings.getAuthorizationSchema(), nullValue());
}
@Test
@EnableFeature(Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ_V2)
public void authorizationSchemaAvailableFeatureV2Enabled() throws Exception {
reconnectAdminClient();
List<ClientRepresentation> clients = testRealm().clients().findByClientId(CLIENT_ID);
assertThat(clients, hasSize(1));
ResourceServerRepresentation authorizationSettings = testRealm().clients().get(clients.get(0).getId()).authorization().getSettings();
assertThat(authorizationSettings, notNullValue());
//admin permissions not enabled for the realm
assertThat(authorizationSettings.getAuthorizationSchema(), nullValue());
try (RealmAttributeUpdater rau = new RealmAttributeUpdater(testRealm()).setAdminPermissionsEnabled(Boolean.TRUE).update()) {
authorizationSettings = testRealm().clients().get(clients.get(0).getId()).authorization().getSettings();
assertThat(authorizationSettings, notNullValue());
//schema should be available only for admin-permissions client
assertThat(authorizationSettings.getAuthorizationSchema(), nullValue());
//get the admin-permissions client
ClientRepresentation adminPermissionsClient = testRealm().toRepresentation().getAdminPermissionsClient();
assertThat(adminPermissionsClient, notNullValue());
authorizationSettings = testRealm().clients().get(adminPermissionsClient.getId()).authorization().getSettings();
assertThat(authorizationSettings.getAuthorizationSchema(), notNullValue());
List<String> scopeNames = testRealm().clients().get(adminPermissionsClient.getId()).authorization().scopes().scopes().stream().map((rep) -> rep.getName()).collect(Collectors.toList());
assertThat(scopeNames, Matchers.hasItem("manage"));
}
}
}