Skip to content

Commit

Permalink
Propagate the javax.annotation.security annotations in REST Data
Browse files Browse the repository at this point in the history
With these changes, the REST Data with Panache extension will propagate the Security annotations within the package `javax.annotation.security` that are defined on your resource interfaces:

```java
import javax.annotation.security.DenyAll;
import javax.annotation.security.RolesAllowed;

@Denyall
@ResourceProperties
public interface PeopleResource extends PanacheEntityResource<Person, Long> {
    @RolesAllowed("superuser")
    boolean delete(Long id);
}
```

Additionally, if you are only interested in specifying the roles that are allowed to use the resources, the `@ResourceProperties` and `@MethodProperties` annotations have the field `rolesAllowed` to list the security roles permitted to access the resource or operation.

Fix quarkusio#28995

(cherry picked from commit e1ae1d8)
  • Loading branch information
Sgitario authored and gsmet committed Dec 14, 2022
1 parent 7edfb6e commit 2717fa1
Show file tree
Hide file tree
Showing 28 changed files with 355 additions and 38 deletions.
20 changes: 20 additions & 0 deletions docs/src/main/asciidoc/rest-data-panache.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,26 @@ Default is `false`.
* `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.

== Securing endpoints

REST Data with Panache will use the Security annotations within the package `javax.annotation.security` that are defined on your resource interfaces:

[source,java]
----
import javax.annotation.security.DenyAll;
import javax.annotation.security.RolesAllowed;
@DenyAll
@ResourceProperties
public interface PeopleResource extends PanacheEntityResource<Person, Long> {
@RolesAllowed("superuser")
boolean delete(Long id);
}
----

Additionally, if you are only interested in specifying the roles that are allowed to use the resources, the `@ResourceProperties` and `@MethodProperties` annotations have the field `rolesAllowed` to list the security roles permitted to access the resource or operation.

== Query parameters

REST Data with Panache supports the following query parameters with the generated resources.
Expand Down
5 changes: 5 additions & 0 deletions docs/src/main/asciidoc/security.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,11 @@ For more information, see the link:{vault-guide}[Quarkus and HashiCorp Vault] do
If your Quarkus Security architecture includes RESTEasy Reactive and Jackson, Quarkus can limit the fields that are included in JSON serialization based on the configured security.
For more information, see xref:resteasy-reactive.adoc#secure-serialization[Writing REST services with RESTEasy Reactive].

== Secure auto-generated resources by REST Data with Panache

If you're using the REST Data with Panache extension to auto-generate your resources, you can still use the Security annotations within the package `javax.annotation.security`.
For more information, see xref:rest-data-panache.adoc#securing-endpoints[Securing auto-generated resources].

== National Vulnerability Database

Most of the Quarkus tags are registered in the US link:https://nvd.nist.gov[National Vulnerability Database] (NVD) in Common Platform Enumeration (CPE) name format.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi;

import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

@MappedSuperclass
public abstract class AbstractEntity<IdType extends Number> {

@Id
@GeneratedValue
private IdType id;

public IdType getId() {
return id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi;

import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;

@MappedSuperclass
public abstract class AbstractItem<IdType extends Number> extends AbstractEntity<IdType> {

private String name;

@ManyToOne(optional = false)
@JsonProperty(access = Access.WRITE_ONLY)
private Collection collection;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi;

import java.util.LinkedList;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
public class Collection {

@Id
private String id;

private String name;

@OneToMany(fetch = FetchType.EAGER, mappedBy = "collection")
private List<Item> items = new LinkedList<>();

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public List<Item> getItems() {
return items;
}

public void setItems(List<Item> items) {
this.items = items;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi;

import javax.enterprise.context.ApplicationScoped;

import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;

@ApplicationScoped
public class CollectionsRepository implements PanacheRepositoryBase<Collection, String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi;

import javax.annotation.security.RolesAllowed;

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", rolesAllowed = "user")
public interface CollectionsResource extends PanacheRepositoryResource<CollectionsRepository, Collection, String> {
@RolesAllowed("superuser")
Collection update(String id, Collection entity);

@MethodProperties(rolesAllowed = "admin")
boolean delete(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi;

import javax.persistence.Entity;

@Entity
public class EmptyListItem extends AbstractItem<Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi;

import javax.enterprise.context.ApplicationScoped;

import io.quarkus.hibernate.orm.panache.PanacheRepository;

@ApplicationScoped
public class EmptyListItemsRepository implements PanacheRepository<EmptyListItem> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi;

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

@ResourceProperties(hal = true)
public interface EmptyListItemsResource extends PanacheRepositoryResource<EmptyListItemsRepository, EmptyListItem, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi;

import javax.persistence.Entity;

@Entity
public class Item extends AbstractItem<Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi;

import javax.enterprise.context.ApplicationScoped;

import io.quarkus.hibernate.orm.panache.PanacheRepository;

@ApplicationScoped
public class ItemsRepository implements PanacheRepository<Item> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.openapi;

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

@ResourceProperties(hal = true)
public interface ItemsResource extends PanacheRepositoryResource<ItemsRepository, Item, Long> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,6 @@
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.builder.Version;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.AbstractEntity;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.AbstractItem;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.Collection;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.CollectionsRepository;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.CollectionsResource;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.EmptyListItem;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.EmptyListItemsRepository;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.EmptyListItemsResource;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.Item;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.ItemsRepository;
import io.quarkus.hibernate.orm.rest.data.panache.deployment.repository.ItemsResource;
import io.quarkus.maven.dependency.Dependency;
import io.quarkus.test.QuarkusProdModeTest;
import io.restassured.RestAssured;
Expand Down Expand Up @@ -73,7 +62,7 @@ public void testOpenApiForGeneratedResources() {
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections/{id}'.put.responses.'201'.content.'application/json'.schema.$ref",
is(COLLECTIONS_SCHEMA_REF))
.body("paths.'/collections/{id}'.put.security[0].SecurityScheme", Matchers.hasItem("user"))
.body("paths.'/collections/{id}'.put.security[0].SecurityScheme", Matchers.hasItem("superuser"))
.body("paths.'/collections/{id}'", Matchers.hasKey("delete"))
.body("paths.'/collections/{id}'.delete.responses", Matchers.hasKey("204"))
.body("paths.'/collections/{id}'.delete.security[0].SecurityScheme", Matchers.hasItem("admin"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
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", rolesAllowed = "user")
@ResourceProperties(hal = true, paged = false, halCollectionName = "item-collections")
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 @@ -8,6 +8,7 @@
import javax.ws.rs.Path;

import org.apache.commons.lang3.StringUtils;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.logging.Logger;

import io.quarkus.deployment.Capabilities;
Expand Down Expand Up @@ -88,6 +89,11 @@ private void implementClassAnnotations(ClassCreator classCreator, ResourceMetada
String className = StringUtils.substringAfterLast(resourceMetadata.getResourceInterface(), ".");
classCreator.addAnnotation(OPENAPI_TAG_ANNOTATION).add("name", className);
}
if (resourceProperties.getClassAnnotations() != null) {
for (AnnotationInstance classAnnotation : resourceProperties.getClassAnnotations()) {
classCreator.addAnnotation(classAnnotation);
}
}
}

private FieldDescriptor implementResourceField(ClassCreator classCreator, ResourceMetadata resourceMetadata) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res

// Add method annotations
addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME));
addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME));
addPostAnnotation(methodCreator);
addConsumesAnnotation(methodCreator, APPLICATION_JSON);
addProducesJsonAnnotation(methodCreator, resourceProperties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,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));
addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME));
addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, Long.class, false);
addSecurityAnnotations(methodCreator, resourceProperties);
if (!isResteasyClassic()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
addDeleteAnnotation(methodCreator);
addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id");
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME));
addOpenApiResponseAnnotation(methodCreator, Response.Status.NO_CONTENT);
addSecurityAnnotations(methodCreator, resourceProperties);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
addPathAnnotation(methodCreator, appendToPath(resourceProperties.getPath(RESOURCE_METHOD_NAME), "{id}"));
addGetAnnotation(methodCreator);
addProducesJsonAnnotation(methodCreator, resourceProperties);
addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME));
addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, resourceMetadata.getEntityType());
addSecurityAnnotations(methodCreator, resourceProperties);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,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);
addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME));
addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, resourceMetadata.getEntityType(), true);
addSecurityAnnotations(methodCreator, resourceProperties);
addSortQueryParamValidatorAnnotation(methodCreator);
Expand Down Expand Up @@ -210,6 +211,7 @@ private void implementNotPaged(ClassCreator classCreator, ResourceMetadata resou
addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME));
addProducesAnnotation(methodCreator, APPLICATION_JSON);
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME));
addOpenApiResponseAnnotation(methodCreator, Response.Status.OK, resourceMetadata.getEntityType(), true);
addSecurityAnnotations(methodCreator, resourceProperties);
addQueryParamAnnotation(methodCreator.getParameterAnnotations(0), "sort");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.rest.data.panache.deployment.methods;

import java.util.Collection;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
Expand All @@ -13,6 +15,7 @@
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.logging.Logger;

import io.quarkus.deployment.Capabilities;
Expand Down Expand Up @@ -154,6 +157,14 @@ protected void addSortQueryParamValidatorAnnotation(AnnotatedElement element) {
element.addAnnotation(SortQueryParamValidator.class);
}

protected void addMethodAnnotations(AnnotatedElement element, Collection<AnnotationInstance> methodAnnotations) {
if (methodAnnotations != null) {
for (AnnotationInstance methodAnnotation : methodAnnotations) {
element.addAnnotation(methodAnnotation);
}
}
}

protected void addSecurityAnnotations(AnnotatedElement element, ResourceProperties resourceProperties) {
String[] rolesAllowed = resourceProperties.getRolesAllowed(getResourceMethodName());
if (rolesAllowed.length > 0 && hasSecurityCapability()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
addConsumesAnnotation(methodCreator, APPLICATION_JSON);
addProducesJsonAnnotation(methodCreator, resourceProperties);
addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_UPDATE_METHOD_NAME));
addOpenApiResponseAnnotation(methodCreator, Response.Status.CREATED, resourceMetadata.getEntityType());
addSecurityAnnotations(methodCreator, resourceProperties);
// Add parameter annotations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ private void implementPaged(ClassCreator classCreator, ResourceMetadata resource
addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME));
addGetAnnotation(methodCreator);
addProducesAnnotation(methodCreator, APPLICATION_HAL_JSON);
addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME));
addSecurityAnnotations(methodCreator, resourceProperties);
addSortQueryParamValidatorAnnotation(methodCreator);
addQueryParamAnnotation(methodCreator.getParameterAnnotations(0), "sort");
Expand Down Expand Up @@ -209,6 +210,7 @@ private void implementNotPaged(ClassCreator classCreator, ResourceMetadata resou
addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME));
addGetAnnotation(methodCreator);
addProducesAnnotation(methodCreator, APPLICATION_HAL_JSON);
addMethodAnnotations(methodCreator, resourceProperties.getMethodAnnotations(RESOURCE_METHOD_NAME));
addSecurityAnnotations(methodCreator, resourceProperties);
addQueryParamAnnotation(methodCreator.getParameterAnnotations(0), "sort");

Expand Down
Loading

0 comments on commit 2717fa1

Please sign in to comment.