diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourcePermissionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourcePermissionRepresentation.java index 80f710675282..d947d38ca5e1 100644 --- a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourcePermissionRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourcePermissionRepresentation.java @@ -23,6 +23,14 @@ public class ResourcePermissionRepresentation extends AbstractPolicyRepresentati private String resourceType; + public ResourcePermissionRepresentation() { + this(null); + } + + public ResourcePermissionRepresentation(String name) { + setName(name); + } + @Override public String getType() { return "resource"; @@ -35,4 +43,4 @@ public void setResourceType(String resourceType) { public String getResourceType() { return resourceType; } -} \ No newline at end of file +} diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java index f737c59021ae..30c1c4c369f2 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java @@ -23,9 +23,11 @@ import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import jakarta.ws.rs.BadRequestException; import org.keycloak.authorization.model.PermissionTicket; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; @@ -41,7 +43,9 @@ import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.common.Profile.Feature; 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; @@ -354,12 +358,12 @@ public Policy create(ResourceServer resourceServer, AbstractPolicyRepresentation }).collect(Collectors.toSet())); } - return RepresentationToModel.toModel(representation, AuthorizationProvider.this, policyStore.create(resourceServer, representation)); + return createSchemaAwarePolicy(RepresentationToModel.toModel(representation, AuthorizationProvider.this, policyStore.create(resourceServer, representation))); } @Override public void delete(String id) { - Policy policy = findById(null, id); + Policy policy = createSchemaAwarePolicy(findById(null, id)); if (policy != null) { ResourceServer resourceServer = policy.getResourceServer(); @@ -388,67 +392,71 @@ public void delete(String id) { @Override public Policy findById(ResourceServer resourceServer, String id) { - return policyStore.findById(resourceServer, id); + return createSchemaAwarePolicy(policyStore.findById(resourceServer, id)); } @Override public Policy findByName(ResourceServer resourceServer, String name) { - return policyStore.findByName(resourceServer, name); + return createSchemaAwarePolicy(policyStore.findByName(resourceServer, name)); } @Override public List findByResourceServer(ResourceServer resourceServer) { - return policyStore.findByResourceServer(resourceServer); + return policyStore.findByResourceServer(resourceServer).stream().map(this::createSchemaAwarePolicy).collect(Collectors.toList()); } @Override public List find(ResourceServer resourceServer, Map attributes, Integer firstResult, Integer maxResults) { - return policyStore.find(resourceServer, attributes, firstResult, maxResults); + return policyStore.find(resourceServer, attributes, firstResult, maxResults).stream().map(this::createSchemaAwarePolicy).collect(Collectors.toList()); } @Override public List findByResource(ResourceServer resourceServer, Resource resource) { - return policyStore.findByResource(resourceServer, resource); + return policyStore.findByResource(resourceServer, resource).stream().map(this::createSchemaAwarePolicy).collect(Collectors.toList()); } @Override public void findByResource(ResourceServer resourceServer, Resource resource, Consumer consumer) { - policyStore.findByResource(resourceServer, resource, consumer); + policyStore.findByResource(resourceServer, resource, policy -> consumer.accept(createSchemaAwarePolicy(policy))); } @Override public List findByResourceType(ResourceServer resourceServer, String resourceType) { - return policyStore.findByResourceType(resourceServer, resourceType); + return policyStore.findByResourceType(resourceServer, resourceType).stream().map(this::createSchemaAwarePolicy).collect(Collectors.toList()); } @Override public List findByScopes(ResourceServer resourceServer, List scopes) { - return policyStore.findByScopes(resourceServer, scopes); + return policyStore.findByScopes(resourceServer, scopes).stream().map(this::createSchemaAwarePolicy).collect(Collectors.toList()); } @Override public List findByScopes(ResourceServer resourceServer, Resource resource, List scopes) { - return policyStore.findByScopes(resourceServer, resource, scopes); + return policyStore.findByScopes(resourceServer, resource, scopes).stream().map(this::createSchemaAwarePolicy).collect(Collectors.toList()); } @Override public void findByScopes(ResourceServer resourceServer, Resource resource, List scopes, Consumer consumer) { - policyStore.findByScopes(resourceServer, resource, scopes, consumer); + policyStore.findByScopes(resourceServer, resource, scopes, policy -> consumer.accept(createSchemaAwarePolicy(policy))); } @Override public List findByType(ResourceServer resourceServer, String type) { - return policyStore.findByType(resourceServer, type); + return policyStore.findByType(resourceServer, type).stream().map(this::createSchemaAwarePolicy).collect(Collectors.toList()); } @Override public List findDependentPolicies(ResourceServer resourceServer, String id) { - return policyStore.findDependentPolicies(resourceServer, id); + return policyStore.findDependentPolicies(resourceServer, id).stream().map(this::createSchemaAwarePolicy).collect(Collectors.toList()); } @Override public void findByResourceType(ResourceServer resourceServer, String type, Consumer policyConsumer) { - policyStore.findByResourceType(resourceServer, type, policyConsumer); + policyStore.findByResourceType(resourceServer, type, policy -> policyConsumer.accept(createSchemaAwarePolicy(policy))); + } + + private Policy createSchemaAwarePolicy(Policy byId) { + return Optional.ofNullable(byId).map((Function) p -> new SchemaAwarePolicy(p, keycloakSession)).orElse(null); } }; } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/SchemaAwarePolicy.java b/server-spi-private/src/main/java/org/keycloak/authorization/SchemaAwarePolicy.java new file mode 100644 index 000000000000..59641d7f213f --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/authorization/SchemaAwarePolicy.java @@ -0,0 +1,181 @@ +/* + * 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.authorization; + +import java.util.Map; +import java.util.Set; + +import jakarta.ws.rs.BadRequestException; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.Resource; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.model.Scope; +import org.keycloak.common.Profile.Feature; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelValidationException; +import org.keycloak.representations.idm.authorization.DecisionStrategy; +import org.keycloak.representations.idm.authorization.Logic; + +public class SchemaAwarePolicy implements Policy { + + private Policy delegate; + private final KeycloakSession session; + + public SchemaAwarePolicy(Policy policy, KeycloakSession session) { + this.delegate = policy; + this.session = session; + checkIfSupportedPolicyType(); + } + + @Override + public String getId() { + return delegate.getId(); + } + + @Override + public String getType() { + return delegate.getType(); + } + + @Override + public DecisionStrategy getDecisionStrategy() { + return delegate.getDecisionStrategy(); + } + + @Override + public void setDecisionStrategy(DecisionStrategy decisionStrategy) { + delegate.setDecisionStrategy(decisionStrategy); + } + + @Override + public Logic getLogic() { + return delegate.getLogic(); + } + + @Override + public void setLogic(Logic logic) { + delegate.setLogic(logic); + } + + @Override + public Map getConfig() { + return delegate.getConfig(); + } + + @Override + public void setConfig(Map config) { + delegate.setConfig(config); + } + + @Override + public void removeConfig(String name) { + delegate.removeConfig(name); + } + + @Override + public void putConfig(String name, String value) { + delegate.putConfig(name, value); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public void setName(String name) { + delegate.setName(name); + } + + @Override + public String getDescription() { + return delegate.getDescription(); + } + + @Override + public void setDescription(String description) { + delegate.setDescription(description); + } + + @Override + public ResourceServer getResourceServer() { + return delegate.getResourceServer(); + } + + @Override + public Set getAssociatedPolicies() { + return delegate.getAssociatedPolicies(); + } + + @Override + public Set getResources() { + return delegate.getResources(); + } + + @Override + public Set getScopes() { + return delegate.getScopes(); + } + + @Override + public String getOwner() { + return delegate.getOwner(); + } + + @Override + public void setOwner(String owner) { + delegate.setOwner(owner); + } + + @Override + public void addScope(Scope scope) { + delegate.addScope(scope); + } + + @Override + public void removeScope(Scope scope) { + delegate.removeScope(scope); + } + + @Override + public void addAssociatedPolicy(Policy associatedPolicy) { + delegate.addAssociatedPolicy(associatedPolicy); + } + + @Override + public void removeAssociatedPolicy(Policy associatedPolicy) { + delegate.removeAssociatedPolicy(associatedPolicy); + } + + @Override + public void addResource(Resource resource) { + delegate.addResource(resource); + } + + @Override + public void removeResource(Resource resource) { + delegate.removeResource(resource); + } + + private void checkIfSupportedPolicyType() throws BadRequestException { + if (AdminPermissionsAuthorizationSchema.INSTANCE.isSupportedPolicyType(session, getResourceServer(), getType())) { + return; + } + + throw new ModelValidationException("Policy type not supported by feature " + Feature.ADMIN_FINE_GRAINED_AUTHZ_V2.getVersionedKey()); } +} diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java index 02de876491d4..89a872098dda 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java @@ -24,10 +24,8 @@ import java.util.function.Predicate; 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 +42,6 @@ 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.AuthorizationProvider; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; @@ -56,7 +53,6 @@ import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.common.Profile.Feature; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.Constants; @@ -94,16 +90,11 @@ public Object getResource(@PathParam("type") String type) { PolicyProviderFactory providerFactory = getPolicyProviderFactory(type); if (providerFactory != null) { - checkIfSupportedPolicyType(type); return doCreatePolicyTypeResource(type); } Policy policy = authorization.getStoreFactory().getPolicyStore().findById(resourceServer, type); - if (policy != null) { - checkIfSupportedPolicyType(policy.getType()); - } - return doCreatePolicyResource(policy); } @@ -144,8 +135,6 @@ protected AbstractPolicyRepresentation doCreateRepresentation(String payload) { throw new RuntimeException("Failed to deserialize representation", cause); } - checkIfSupportedPolicyType(representation.getType()); - return representation; } @@ -292,7 +281,6 @@ public Response findAll(@QueryParam("policyId") String id, } protected AbstractPolicyRepresentation toRepresentation(Policy model, String fields, AuthorizationProvider authorization) { - checkIfSupportedPolicyType(model.getType()); return ModelToRepresentation.toRepresentation(model, authorization, true, false, fields != null && fields.equals("*")); } @@ -356,12 +344,4 @@ private void audit(AbstractPolicyRepresentation resource, String id, OperationTy adminEvent.operation(operation).resourcePath(session.getContext().getUri()).representation(resource).success(); } } - - private void checkIfSupportedPolicyType(String type) throws BadRequestException { - if (AdminPermissionsAuthorizationSchema.INSTANCE.isSupportedPolicyType(authorization.getKeycloakSession(), resourceServer, type)) { - return; - } - - throw new BadRequestException("Policy type not supported by feature " + Feature.ADMIN_FINE_GRAINED_AUTHZ_V2.getVersionedKey()); - } } diff --git a/tests/base/src/test/java/org/keycloak/test/admin/authz/fgap/PermissionClientTest.java b/tests/base/src/test/java/org/keycloak/test/admin/authz/fgap/PermissionClientTest.java index 632da6306aff..45905187d82f 100644 --- a/tests/base/src/test/java/org/keycloak/test/admin/authz/fgap/PermissionClientTest.java +++ b/tests/base/src/test/java/org/keycloak/test/admin/authz/fgap/PermissionClientTest.java @@ -25,6 +25,7 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; import org.junit.jupiter.api.Test; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.authorization.AggregatePolicyRepresentation; import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation; import org.keycloak.representations.idm.authorization.ClientScopePolicyRepresentation; @@ -44,7 +45,7 @@ public class PermissionClientTest extends AbstractPermissionTest { @Test public void testUnsupportedPolicyTypes() { - assertSupportForPolicyType("resource", () -> getPermissionsResource().resource().create(new ResourcePermissionRepresentation()), false); + assertSupportForPolicyType("resource", () -> getPermissionsResource().resource().create(new ResourcePermissionRepresentation(KeycloakModelUtils.generateId())), false); } @Test @@ -68,6 +69,7 @@ private void assertSupportForPolicyType(String type, Supplier operatio PolicyRepresentation representation = new PolicyRepresentation(); + representation.setName(KeycloakModelUtils.generateId()); representation.setType(type); try (Response response = getPolicies().create(representation)) { @@ -77,6 +79,5 @@ private void assertSupportForPolicyType(String type, Supplier operatio private void assertPolicyEndpointResponse(String type, boolean supported, Response response) { assertThat("Policy type [" + type + "] should be " + (supported ? "supported" : "unsupported"), Status.BAD_REQUEST.equals(Status.fromStatusCode(response.getStatus())), not(supported)); - assertThat("Policy type [" + type + "] should be " + (supported ? "supported" : "unsupported"), response.readEntity(String.class).contains("Policy type not supported by feature"), not(supported)); } }