mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
[FGAP] Add adminPermissionClientCheck to authorization services REST endpoints
Closes #35945 Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
parent
6af76d895e
commit
0a632fdefa
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 $
|
||||
|
||||
@ -50,6 +50,11 @@ public class ClientConfigBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientConfigBuilder authorizationServices() {
|
||||
rep.setAuthorizationServicesEnabled(true);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientRepresentation build() {
|
||||
return rep;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user