Skip to content

Commit 19b0af3

Browse files
committed
feat(security,hibernate-orm): support security annotations on repositories
1 parent 88b0c0a commit 19b0af3

25 files changed

+1246
-8
lines changed

docs/src/main/asciidoc/hibernate-orm.adoc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1949,6 +1949,40 @@ Please refer to the corresponding https://hibernate.org/repositories/[Hibernate
19491949
and https://jakarta.ee/specifications/data/1.0/jakarta-data-1.0[Jakarta Data]
19501950
guides to learn what else they have to offer.
19511951

1952+
==== Secure Jakarta Data repositories
1953+
1954+
Quarkus Security supports security annotations placed directly on Jakarta Data repository interfaces and their methods.
1955+
This is one exception to the general rule outlined in xref:security-authorize-web-endpoints-reference.adoc#security-annotations-inheritance[security annotations inheritance], as we do not recommend using security annotations on interfaces elsewhere.
1956+
1957+
Security annotations can be placed either directly on the Jakarta Data repository methods:
1958+
1959+
[source,java]
1960+
----
1961+
@Repository
1962+
public interface MyRepository extends CrudRepository<MyEntity, Integer> {
1963+
1964+
@RolesAllowed("admin")
1965+
@Delete
1966+
void delete(String name);
1967+
1968+
}
1969+
----
1970+
1971+
or on the interface classes:
1972+
1973+
[source,java]
1974+
----
1975+
@Authenticated
1976+
@Repository
1977+
public interface MyRepository extends CrudRepository<MyEntity, Integer> {
1978+
1979+
@Delete
1980+
void delete(String name); <1>
1981+
1982+
}
1983+
----
1984+
<1> Only the methods directly declared on the `MyRepository` interface require authentication. Methods inherited from `CrudRepository`, such as the `insert` method, are not secured by the interface-level annotation.
1985+
19521986
[[configuration-reference]]
19531987
== Configuration Reference for Hibernate ORM
19541988

docs/src/main/asciidoc/security-authorize-web-endpoints-reference.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,7 @@ It is possible to use multiple expressions in the role definition.
790790
In development mode, it allows any authenticated user.
791791
<5> Property expression `all-roles` will be treated as a collection type `List`, therefore, the endpoint will be accessible for roles `Administrator`, `Software`, `Tester` and `User`.
792792

793+
[[security-annotations-inheritance]]
793794
=== Endpoint security annotations and Jakarta REST inheritance
794795

795796
Quarkus supports security annotations placed on the endpoint implementation or its class like in the example below:

extensions/hibernate-orm/deployment/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
<groupId>io.quarkus</groupId>
6262
<artifactId>quarkus-hibernate-validator-spi</artifactId>
6363
</dependency>
64+
<dependency>
65+
<groupId>io.quarkus</groupId>
66+
<artifactId>quarkus-security-spi</artifactId>
67+
</dependency>
6468

6569
<dependency>
6670
<groupId>io.quarkus</groupId>

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146
import io.quarkus.panache.hibernate.common.deployment.HibernateModelClassCandidatesForFieldAccessBuildItem;
147147
import io.quarkus.runtime.LaunchMode;
148148
import io.quarkus.runtime.configuration.ConfigurationException;
149+
import io.quarkus.security.spi.SecuredInterfaceAnnotationBuildItem;
149150
import net.bytebuddy.description.type.TypeDescription;
150151
import net.bytebuddy.dynamic.ClassFileLocator;
151152
import net.bytebuddy.dynamic.DynamicType;
@@ -171,6 +172,8 @@ public final class HibernateOrmProcessor {
171172

172173
private static final String INTEGRATOR_SERVICE_FILE = "META-INF/services/org.hibernate.integrator.spi.Integrator";
173174

175+
private static final String JAKARTA_DATA_REPOSITORY_ANNOTATION = "jakarta.data.repository.Repository";
176+
174177
@BuildStep
175178
NativeImageFeatureBuildItem registerServicesForReflection(BuildProducer<ServiceProviderBuildItem> services) {
176179
for (DotName serviceProvider : ClassNames.SERVICE_PROVIDERS) {
@@ -837,6 +840,15 @@ public void registerInjectServiceMethodsForReflection(CombinedIndexBuildItem ind
837840
}
838841
}
839842

843+
@BuildStep
844+
public void registerJakartaDataRepositorySecurityAnnotations(Capabilities capabilities,
845+
BuildProducer<SecuredInterfaceAnnotationBuildItem> securedInterfaceAnnotationProducer) {
846+
if (capabilities.isPresent(Capability.SECURITY)) {
847+
securedInterfaceAnnotationProducer
848+
.produce(new SecuredInterfaceAnnotationBuildItem(DotName.createSimple(JAKARTA_DATA_REPOSITORY_ANNOTATION)));
849+
}
850+
}
851+
840852
private void handleHibernateORMWithNoPersistenceXml(
841853
HibernateOrmConfig hibernateOrmConfig,
842854
CombinedIndexBuildItem index,

extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
import io.quarkus.security.spi.PermissionsAllowedMetaAnnotationBuildItem;
144144
import io.quarkus.security.spi.RegisterClassSecurityCheckBuildItem;
145145
import io.quarkus.security.spi.RolesAllowedConfigExpResolverBuildItem;
146+
import io.quarkus.security.spi.SecuredInterfaceAnnotationBuildItem;
146147
import io.quarkus.security.spi.SecurityTransformer;
147148
import io.quarkus.security.spi.SecurityTransformer.AuthorizationType;
148149
import io.quarkus.security.spi.SecurityTransformerBuildItem;
@@ -168,14 +169,24 @@ public class SecurityProcessor {
168169

169170
@BuildStep
170171
SecurityTransformerBuildItem createSecurityTransformerBuildItem(
172+
List<SecuredInterfaceAnnotationBuildItem> securedInterfacePredicates,
171173
List<AdditionalSecurityAnnotationBuildItem> additionalSecurityAnnotationBuildItems) {
172174
// collect security annotations
173175
Map<AuthorizationType, Set<DotName>> authorizationTypeToSecurityAnnotations = new EnumMap<>(AuthorizationType.class);
174176
authorizationTypeToSecurityAnnotations.put(SECURITY_CHECK, new HashSet<>(SECURITY_CHECK_ANNOTATIONS));
175177
additionalSecurityAnnotationBuildItems.forEach(i -> authorizationTypeToSecurityAnnotations
176178
.computeIfAbsent(i.getAuthorizationType(), k -> new HashSet<>()).add(i.getSecurityAnnotationName()));
177179

178-
return new SecurityTransformerBuildItem(authorizationTypeToSecurityAnnotations);
180+
Predicate<ClassInfo> isInterfaceWithTransformations = securedInterfacePredicates.stream()
181+
.map(SecuredInterfaceAnnotationBuildItem::getIsInterfaceWithTransformations)
182+
.reduce(Predicate::or)
183+
.orElse(null);
184+
Set<DotName> securedInterfaceAnnotations = securedInterfacePredicates.stream()
185+
.map(SecuredInterfaceAnnotationBuildItem::getInterfaceAnnotationName)
186+
.collect(Collectors.toSet());
187+
188+
return new SecurityTransformerBuildItem(authorizationTypeToSecurityAnnotations,
189+
isInterfaceWithTransformations, securedInterfaceAnnotations);
179190
}
180191

181192
@BuildStep
@@ -187,6 +198,19 @@ List<AdditionalIndexedClassesBuildItem> registerAdditionalIndexedClassesBuildIte
187198
.of(new AdditionalIndexedClassesBuildItem(securityTransformerBuildItem.getAllSecurityAnnotationNames()));
188199
}
189200

201+
@BuildStep
202+
void secureInterfaceImplementations(SecurityTransformerBuildItem securityTransformerBuildItem,
203+
CombinedIndexBuildItem combinedIndexBuildItem,
204+
BuildProducer<AnnotationsTransformerBuildItem> annotationsTransformerProducer) {
205+
SecurityTransformer securityTransformer = createSecurityTransformer(
206+
combinedIndexBuildItem.getIndex(), securityTransformerBuildItem);
207+
var annotationTransformations = securityTransformer.getInterfaceTransformations();
208+
if (annotationTransformations != null) {
209+
annotationTransformations
210+
.forEach(i -> annotationsTransformerProducer.produce(new AnnotationsTransformerBuildItem(i)));
211+
}
212+
}
213+
190214
/**
191215
* Create JCAProviderBuildItems for any configured provider names
192216
*/
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.quarkus.security.spi;
2+
3+
import java.util.Objects;
4+
import java.util.function.Predicate;
5+
6+
import org.jboss.jandex.ClassInfo;
7+
import org.jboss.jandex.DotName;
8+
9+
import io.quarkus.builder.item.MultiBuildItem;
10+
11+
/**
12+
* Security annotations on interfaces are in most cases not inherited by interface implementors.
13+
* This build item allows to register interfaces whose implementors will inherit security annotations.
14+
* If this build item is used extensively, it can get very complex, and we would create situations,
15+
* where users can hardly tell precedence, like what will happen if different repeatable annotation instance
16+
* (like the {@code PermissionsAllowed} annotation) is placed on both implemented and interface method.
17+
* Or does method-level interface annotation take precedence over implementors class-level annotation.
18+
* Examples could continue, for which reason we aim to support simple cases for now.
19+
* <p>
20+
* The all the implementors of interfaces matched by this build item annotation will inherit
21+
* security annotations from interface methods and the interface class-level security annotations only
22+
* apply directly on the methods declared on the interface. All scenarios that are supposed to work are tested.
23+
* Any scenario that is not working is not yet supported.
24+
*/
25+
public final class SecuredInterfaceAnnotationBuildItem extends MultiBuildItem {
26+
27+
private final DotName interfaceAnnotationName;
28+
29+
public SecuredInterfaceAnnotationBuildItem(DotName interfaceAnnotationName) {
30+
this.interfaceAnnotationName = Objects.requireNonNull(interfaceAnnotationName);
31+
}
32+
33+
public Predicate<ClassInfo> getIsInterfaceWithTransformations() {
34+
return ci -> ci.isInterface() && ci.hasDeclaredAnnotation(interfaceAnnotationName);
35+
}
36+
37+
public DotName getInterfaceAnnotationName() {
38+
return interfaceAnnotationName;
39+
}
40+
}

0 commit comments

Comments
 (0)