Skip to content

Commit

Permalink
Support setting the RolesAllowed in the Panache REST Data extension
Browse files Browse the repository at this point in the history
With these changes, we can specify the roles using the annotations `@ResourceProperties` at resource level, and `@MethodProperties` for method level.
Fix quarkusio#28507
  • Loading branch information
Sgitario authored and tmihalac committed Oct 27, 2022
1 parent 610e835 commit d24dc0f
Show file tree
Hide file tree
Showing 23 changed files with 147 additions and 60 deletions.
2 changes: 2 additions & 0 deletions docs/src/main/asciidoc/rest-data-panache.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ public interface PeopleResource extends PanacheEntityResource<Person, Long> {

* `exposed` - whether resource could be exposed. A global resource property that can be overridden for each method. Default is `true`.
* `path` - resource base path. Default path is a hyphenated lowercase resource name without a suffix of `resource` or `controller`.
* `rolesAllowed` - List of the security roles permitted to access the resources. It needs a Quarkus security extension to be present, otherwise it will be ignored. Default is empty.
* `paged` - whether collection responses should be paged or not.
First, last, previous and next page URIs are included in the response headers if they exist.
Request page index and size are taken from the `page` and `size` query parameters that default to `0` and `20` respectively.
Expand All @@ -322,6 +323,7 @@ Default is `false`.

* `exposed` - does not expose a particular HTTP verb when set to `false`. Default is `true`.
* `path` - operation path (this is appended to the resource base path). Default is an empty string.
* `rolesAllowed` - List of the security roles permitted to access this operation. It needs a Quarkus security extension to be present, otherwise it will be ignored. Default is empty.

== Query parameters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ class OpenApiIntegrationTest {
.setForcedDependencies(List.of(
Dependency.of("io.quarkus", "quarkus-smallrye-openapi", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-jdbc-h2-deployment", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-resteasy-jsonb-deployment", Version.getVersion())))
Dependency.of("io.quarkus", "quarkus-resteasy-jsonb-deployment", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-security-deployment", Version.getVersion())))
.setRun(true);

@Test
Expand All @@ -52,9 +53,13 @@ public void testOpenApiForGeneratedResources() {
.body("paths.'/collections'.get.tags", Matchers.hasItem("CollectionsResource"))
.body("paths.'/collections'", Matchers.hasKey("post"))
.body("paths.'/collections'.post.tags", Matchers.hasItem("CollectionsResource"))
.body("paths.'/collections'.post.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'", Matchers.hasKey("get"))
.body("paths.'/collections/{id}'.get.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'", Matchers.hasKey("put"))
.body("paths.'/collections/{id}'.put.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'", Matchers.hasKey("delete"))
.body("paths.'/collections/{id}'.delete.security[0].SecurityScheme", Matchers.hasItem("admin"))
.body("paths.'/empty-list-items'", Matchers.hasKey("get"))
.body("paths.'/empty-list-items'.get.tags", Matchers.hasItem("EmptyListItemsResource"))
.body("paths.'/empty-list-items'", Matchers.hasKey("post"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.repository;

import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource;
import io.quarkus.rest.data.panache.MethodProperties;
import io.quarkus.rest.data.panache.ResourceProperties;

@ResourceProperties(hal = true, paged = false, halCollectionName = "item-collections")
@ResourceProperties(hal = true, paged = false, halCollectionName = "item-collections", rolesAllowed = "user")
public interface CollectionsResource extends PanacheRepositoryResource<CollectionsRepository, Collection, String> {
@MethodProperties(rolesAllowed = "admin")
boolean delete(String name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ class OpenApiIntegrationTest {
.setForcedDependencies(List.of(
Dependency.of("io.quarkus", "quarkus-smallrye-openapi", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-reactive-pg-client-deployment", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-resteasy-reactive-jsonb-deployment", Version.getVersion())))
Dependency.of("io.quarkus", "quarkus-resteasy-reactive-jsonb-deployment", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-security-deployment", Version.getVersion())))
.setRun(true);

@Test
Expand All @@ -52,9 +53,13 @@ public void testOpenApiForGeneratedResources() {
.body("paths.'/collections'.get.tags", Matchers.hasItem("CollectionsResource"))
.body("paths.'/collections'", Matchers.hasKey("post"))
.body("paths.'/collections'.post.tags", Matchers.hasItem("CollectionsResource"))
.body("paths.'/collections'.post.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'", Matchers.hasKey("get"))
.body("paths.'/collections/{id}'.get.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'", Matchers.hasKey("put"))
.body("paths.'/collections/{id}'.put.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'", Matchers.hasKey("delete"))
.body("paths.'/collections/{id}'.delete.security[0].SecurityScheme", Matchers.hasItem("admin"))
.body("paths.'/empty-list-items'", Matchers.hasKey("get"))
.body("paths.'/empty-list-items'.get.tags", Matchers.hasItem("EmptyListItemsResource"))
.body("paths.'/empty-list-items'", Matchers.hasKey("post"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.quarkus.hibernate.reactive.rest.data.panache.deployment.repository;

import io.quarkus.hibernate.reactive.rest.data.panache.PanacheRepositoryResource;
import io.quarkus.rest.data.panache.MethodProperties;
import io.quarkus.rest.data.panache.ResourceProperties;

@ResourceProperties(hal = true, paged = false, halCollectionName = "item-collections")
@ResourceProperties(hal = true, paged = false, halCollectionName = "item-collections", rolesAllowed = "user")
public interface CollectionsResource extends PanacheRepositoryResource<CollectionsRepository, Collection, String> {
@MethodProperties(rolesAllowed = "admin")
boolean delete(String name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ class JaxRsResourceImplementor {

private final List<MethodImplementor> methodImplementors;

JaxRsResourceImplementor(boolean withValidation, boolean isResteasyClassic, boolean isReactivePanache) {
this.methodImplementors = Arrays.asList(new GetMethodImplementor(isResteasyClassic, isReactivePanache),
new ListMethodImplementor(isResteasyClassic, isReactivePanache),
new CountMethodImplementor(isResteasyClassic, isReactivePanache),
new AddMethodImplementor(withValidation, isResteasyClassic, isReactivePanache),
new UpdateMethodImplementor(withValidation, isResteasyClassic, isReactivePanache),
new DeleteMethodImplementor(isResteasyClassic, isReactivePanache),
JaxRsResourceImplementor(Capabilities capabilities) {
this.methodImplementors = Arrays.asList(new GetMethodImplementor(capabilities),
new ListMethodImplementor(capabilities),
new CountMethodImplementor(capabilities),
new AddMethodImplementor(capabilities),
new UpdateMethodImplementor(capabilities),
new DeleteMethodImplementor(capabilities),
// The list hal endpoint needs to be added for both resteasy classic and resteasy reactive
// because the pagination links are programmatically added.
new ListHalMethodImplementor(isResteasyClassic, isReactivePanache));
new ListHalMethodImplementor(capabilities));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ void implementResources(CombinedIndexBuildItem index, List<RestDataResourceBuild

ClassOutput classOutput = isResteasyClassic ? new GeneratedBeanGizmoAdaptor(resteasyClassicImplementationsProducer)
: new GeneratedJaxRsResourceGizmoAdaptor(resteasyReactiveImplementationsProducer);
JaxRsResourceImplementor jaxRsResourceImplementor = new JaxRsResourceImplementor(hasValidatorCapability(capabilities),
isResteasyClassic, isReactivePanache);
JaxRsResourceImplementor jaxRsResourceImplementor = new JaxRsResourceImplementor(capabilities);
ResourcePropertiesProvider resourcePropertiesProvider = new ResourcePropertiesProvider(index.getIndex());

for (RestDataResourceBuildItem resourceBuildItem : resourceBuildItems) {
Expand Down Expand Up @@ -100,10 +99,6 @@ private ResourceProperties getResourceProperties(ResourcePropertiesProvider reso
return resourcePropertiesProvider.getForInterface(resourceMetadata.getResourceInterface());
}

private boolean hasValidatorCapability(Capabilities capabilities) {
return capabilities.isPresent(Capability.HIBERNATE_VALIDATOR);
}

private boolean hasAnyJsonCapabilityForResteasyClassic(Capabilities capabilities) {
return capabilities.isPresent(Capability.RESTEASY_JSON_JSONB)
|| capabilities.isPresent(Capability.RESTEASY_JSON_JACKSON);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import javax.validation.Valid;
import javax.ws.rs.core.Response;

import io.quarkus.deployment.Capabilities;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.FieldDescriptor;
import io.quarkus.gizmo.MethodCreator;
Expand All @@ -28,11 +29,8 @@ public final class AddMethodImplementor extends StandardMethodImplementor {

private static final String REL = "add";

private final boolean withValidation;

public AddMethodImplementor(boolean withValidation, boolean isResteasyClassic, boolean isReactivePanache) {
super(isResteasyClassic, isReactivePanache);
this.withValidation = withValidation;
public AddMethodImplementor(Capabilities capabilities) {
super(capabilities);
}

/**
Expand Down Expand Up @@ -112,8 +110,9 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
addConsumesAnnotation(methodCreator, APPLICATION_JSON);
addProducesJsonAnnotation(methodCreator, resourceProperties);
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
addSecurityAnnotations(methodCreator, resourceProperties);
// Add parameter annotations
if (withValidation) {
if (hasValidatorCapability()) {
methodCreator.getParameterAnnotations(0).addAnnotation(Valid.class);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import javax.ws.rs.core.Response;

import io.quarkus.deployment.Capabilities;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.FieldDescriptor;
import io.quarkus.gizmo.MethodCreator;
Expand All @@ -27,8 +28,8 @@ public final class CountMethodImplementor extends StandardMethodImplementor {

private static final String REL = "count";

public CountMethodImplementor(boolean isResteasyClassic, boolean isReactivePanache) {
super(isResteasyClassic, isReactivePanache);
public CountMethodImplementor(Capabilities capabilities) {
super(capabilities);
}

/**
Expand Down Expand Up @@ -79,6 +80,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
addGetAnnotation(methodCreator);
addProducesAnnotation(methodCreator, APPLICATION_JSON);
addPathAnnotation(methodCreator, appendToPath(resourceProperties.getPath(RESOURCE_METHOD_NAME), RESOURCE_METHOD_NAME));
addSecurityAnnotations(methodCreator, resourceProperties);
if (!isResteasyClassic()) {
// We only add the Links annotation in Resteasy Reactive because Resteasy Classic ignores the REL parameter:
// it always uses "list" for GET methods, so it interferes with the list implementation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import javax.ws.rs.core.Response;

import io.quarkus.deployment.Capabilities;
import io.quarkus.gizmo.BranchResult;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.FieldDescriptor;
Expand All @@ -28,8 +29,8 @@ public final class DeleteMethodImplementor extends StandardMethodImplementor {

private static final String REL = "remove";

public DeleteMethodImplementor(boolean isResteasyClassic, boolean isReactivePanache) {
super(isResteasyClassic, isReactivePanache);
public DeleteMethodImplementor(Capabilities capabilities) {
super(capabilities);
}

/**
Expand Down Expand Up @@ -89,6 +90,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
addDeleteAnnotation(methodCreator);
addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id");
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
addSecurityAnnotations(methodCreator, resourceProperties);

ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis());
ResultHandle id = methodCreator.getMethodParam(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import javax.ws.rs.core.Response;

import io.quarkus.deployment.Capabilities;
import io.quarkus.gizmo.BranchResult;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.FieldDescriptor;
Expand All @@ -28,8 +29,8 @@ public final class GetMethodImplementor extends StandardMethodImplementor {

private static final String REL = "self";

public GetMethodImplementor(boolean isResteasyClassic, boolean isReactivePanache) {
super(isResteasyClassic, isReactivePanache);
public GetMethodImplementor(Capabilities capabilities) {
super(capabilities);
}

/**
Expand Down Expand Up @@ -90,6 +91,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
addPathAnnotation(methodCreator, appendToPath(resourceProperties.getPath(RESOURCE_METHOD_NAME), "{id}"));
addGetAnnotation(methodCreator);
addProducesJsonAnnotation(methodCreator, resourceProperties);
addSecurityAnnotations(methodCreator, resourceProperties);

addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id");
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import io.quarkus.deployment.Capabilities;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.FieldDescriptor;
import io.quarkus.gizmo.MethodCreator;
Expand Down Expand Up @@ -41,8 +42,8 @@ public final class ListMethodImplementor extends StandardMethodImplementor {

private final SortImplementor sortImplementor = new SortImplementor();

public ListMethodImplementor(boolean isResteasyClassic, boolean isReactivePanache) {
super(isResteasyClassic, isReactivePanache);
public ListMethodImplementor(Capabilities capabilities) {
super(capabilities);

this.paginationImplementor = new PaginationImplementor();
}
Expand Down Expand Up @@ -142,6 +143,7 @@ private void implementPaged(ClassCreator classCreator, ResourceMetadata resource
addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME));
addProducesAnnotation(methodCreator, APPLICATION_JSON);
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
addSecurityAnnotations(methodCreator, resourceProperties);
addSortQueryParamValidatorAnnotation(methodCreator);
addQueryParamAnnotation(methodCreator.getParameterAnnotations(0), "sort");
addQueryParamAnnotation(methodCreator.getParameterAnnotations(1), "page");
Expand Down Expand Up @@ -207,6 +209,7 @@ private void implementNotPaged(ClassCreator classCreator, ResourceMetadata resou
addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME));
addProducesAnnotation(methodCreator, APPLICATION_JSON);
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
addSecurityAnnotations(methodCreator, resourceProperties);
addQueryParamAnnotation(methodCreator.getParameterAnnotations(0), "sort");

ResultHandle sortQuery = methodCreator.getMethodParam(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import org.jboss.logging.Logger;

import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.gizmo.AnnotatedElement;
import io.quarkus.gizmo.AnnotationCreator;
import io.quarkus.gizmo.BytecodeCreator;
Expand All @@ -32,16 +34,15 @@
*/
public abstract class StandardMethodImplementor implements MethodImplementor {

private static final String ROLES_ALLOWED_ANNOTATION = "javax.annotation.security.RolesAllowed";
private static final Logger LOGGER = Logger.getLogger(StandardMethodImplementor.class);

protected final ResponseImplementor responseImplementor;
private final boolean isResteasyClassic;
private final boolean isReactivePanache;
private final Capabilities capabilities;

protected StandardMethodImplementor(boolean isResteasyClassic, boolean isReactivePanache) {
this.isResteasyClassic = isResteasyClassic;
this.isReactivePanache = isReactivePanache;
this.responseImplementor = new ResponseImplementor(isResteasyClassic);
protected StandardMethodImplementor(Capabilities capabilities) {
this.capabilities = capabilities;
this.responseImplementor = new ResponseImplementor(capabilities);
}

/**
Expand Down Expand Up @@ -91,7 +92,7 @@ protected void addDeleteAnnotation(AnnotatedElement element) {
}

protected void addLinksAnnotation(AnnotatedElement element, String entityClassName, String rel) {
if (isResteasyClassic) {
if (isResteasyClassic()) {
AnnotationCreator linkResource = element.addAnnotation("org.jboss.resteasy.links.LinkResource");
linkResource.addValue("entityClassName", entityClassName);
linkResource.addValue("rel", rel);
Expand Down Expand Up @@ -148,6 +149,13 @@ protected void addSortQueryParamValidatorAnnotation(AnnotatedElement element) {
element.addAnnotation(SortQueryParamValidator.class);
}

protected void addSecurityAnnotations(AnnotatedElement element, ResourceProperties resourceProperties) {
String[] rolesAllowed = resourceProperties.getRolesAllowed(getResourceMethodName());
if (rolesAllowed.length > 0 && hasSecurityCapability()) {
element.addAnnotation(ROLES_ALLOWED_ANNOTATION).add("value", rolesAllowed);
}
}

protected String appendToPath(String path, String suffix) {
if (path.endsWith("/")) {
path = path.substring(0, path.lastIndexOf("/"));
Expand All @@ -158,11 +166,19 @@ protected String appendToPath(String path, String suffix) {
return String.join("/", path, suffix);
}

protected boolean hasSecurityCapability() {
return capabilities.isPresent(Capability.SECURITY);
}

protected boolean hasValidatorCapability() {
return capabilities.isPresent(Capability.HIBERNATE_VALIDATOR);
}

protected boolean isResteasyClassic() {
return isResteasyClassic;
return capabilities.isPresent(Capability.RESTEASY);
}

protected boolean isNotReactivePanache() {
return !isReactivePanache;
return !capabilities.isPresent(Capability.HIBERNATE_REACTIVE);
}
}
Loading

0 comments on commit d24dc0f

Please sign in to comment.