io.quarkiverse.zanzibar.jaxrs.ZanzibarDynamicFeature Maven / Gradle / Ivy
package io.quarkiverse.zanzibar.jaxrs;
import static io.quarkiverse.zanzibar.annotations.FGADynamicObject.Source.*;
import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.DynamicFeature;
import jakarta.ws.rs.container.ResourceInfo;
import jakarta.ws.rs.core.FeatureContext;
import org.jboss.logging.Logger;
import io.quarkiverse.zanzibar.RelationshipManager;
import io.quarkiverse.zanzibar.UserIdExtractor;
import io.quarkiverse.zanzibar.annotations.*;
import io.quarkiverse.zanzibar.jaxrs.ZanzibarAuthorizationFilter.Action;
public class ZanzibarDynamicFeature implements DynamicFeature {
private static final Logger log = Logger.getLogger(ZanzibarDynamicFeature.class);
public interface FilterFactory {
ContainerRequestFilter create(Action annotations, RelationshipManager relationshipManager,
UserIdExtractor userIdExtractor, Optional userType, Optional unauthenticatedUserId,
Duration timeout);
}
static class AnnotationQuery {
public Object source;
public Class extends Annotation> annotationType;
public AnnotationQuery(Object source, Class extends Annotation> annotationType) {
this.source = source;
this.annotationType = annotationType;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof AnnotationQuery))
return false;
AnnotationQuery that = (AnnotationQuery) o;
return source.equals(that.source) && annotationType.equals(that.annotationType);
}
@Override
public int hashCode() {
return Objects.hash(source, annotationType);
}
}
static class Annotations {
final Optional dynamicObject;
final Optional constantObject;
final Optional relationAllowed;
final Optional userType;
Annotations(Optional dynamicObject, Optional constantObject,
Optional relationAllowed, Optional userType) {
this.dynamicObject = dynamicObject;
this.constantObject = constantObject;
this.relationAllowed = relationAllowed;
this.userType = userType;
}
boolean isEmpty() {
return dynamicObject.isEmpty() && constantObject.isEmpty() && relationAllowed.isEmpty();
}
}
RelationshipManager relationshipManager;
UserIdExtractor userIdExtractor;
Optional unauthenticatedUserId;
Duration timeout;
boolean denyUnannotated;
FilterFactory filterFactory;
Map filterCache = new ConcurrentHashMap<>();
Map authorizationAnnotationsCache = new ConcurrentHashMap<>();
Map> annotationQueryCache = new ConcurrentHashMap<>();
public ZanzibarDynamicFeature(RelationshipManager relationshipManager, UserIdExtractor userIdExtractor,
Optional unauthenticatedUserId,
Duration timeout, boolean denyUnannotated, FilterFactory filterFactory) {
this.relationshipManager = Objects.requireNonNull(relationshipManager, "relationshipManager must not be null");
this.userIdExtractor = Objects.requireNonNull(userIdExtractor, "userIdExtractor must not be null");
this.unauthenticatedUserId = unauthenticatedUserId;
this.timeout = timeout;
this.denyUnannotated = denyUnannotated;
this.filterFactory = filterFactory;
}
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
var annotations = findAuthorizationAnnotations(resourceInfo);
if (annotations.isEmpty()) {
if (denyUnannotated) {
context.register(ZanzibarDenyFilter.INSTANCE);
}
return;
}
if (annotations.relationAllowed.isEmpty()) {
String message = "No FGA relation specifier found for method " + resourceInfo.getResourceMethod();
throw new IllegalStateException(message);
}
var relation = annotations.relationAllowed.get();
// Check for public/any access
if (annotations.relationAllowed.get().value().equals(FGARelation.ANY)) {
log.debugf("Skipping authorization checks for %f, any relation is allowed", resourceInfo.getResourceMethod());
return;
}
Action action;
if (annotations.constantObject.isPresent()) {
action = new Action(annotations.constantObject.get(), relation.value());
} else if (annotations.dynamicObject.isPresent()) {
action = new Action(annotations.dynamicObject.get(), relation.value());
} else {
String message = "No FGA object specifier found for method " + resourceInfo.getResourceMethod();
throw new IllegalStateException(message);
}
Optional userType = annotations.userType.map(FGAUserType::value);
var filter = filterCache.computeIfAbsent(action,
key -> filterFactory.create(key, relationshipManager, userIdExtractor, userType, unauthenticatedUserId,
timeout));
context.register(filter, Priorities.AUTHORIZATION);
}
Annotations findAuthorizationAnnotations(ResourceInfo resourceInfo) {
return authorizationAnnotationsCache.computeIfAbsent(resourceInfo.getResourceMethod(), key -> {
var userTypeAnn = findAnnotation(resourceInfo, FGAUserType.class);
var relationAllowedAnn = findAnnotation(resourceInfo, FGARelation.class);
var constantObjectAnn = findAnnotation(resourceInfo, FGAObject.class);
var dynamicObjectAnn = findAnnotation(resourceInfo, FGADynamicObject.class);
if (dynamicObjectAnn.isEmpty()) {
var pathObjectAnn = findAnnotation(resourceInfo, FGAPathObject.class);
if (pathObjectAnn.isPresent()) {
dynamicObjectAnn = Optional.of(
new FGADynamicObject.Literal(PATH, pathObjectAnn.get().param(), pathObjectAnn.get().type()));
}
}
if (dynamicObjectAnn.isEmpty()) {
var queryObjectAnn = findAnnotation(resourceInfo, FGAQueryObject.class);
if (queryObjectAnn.isPresent()) {
dynamicObjectAnn = Optional.of(
new FGADynamicObject.Literal(QUERY, queryObjectAnn.get().param(), queryObjectAnn.get().type()));
}
}
if (dynamicObjectAnn.isEmpty()) {
var headerObjectAnn = findAnnotation(resourceInfo, FGAHeaderObject.class);
if (headerObjectAnn.isPresent()) {
dynamicObjectAnn = Optional.of(
new FGADynamicObject.Literal(HEADER, headerObjectAnn.get().name(), headerObjectAnn.get().type()));
}
}
if (dynamicObjectAnn.isEmpty()) {
var requestObjectAnn = findAnnotation(resourceInfo, FGARequestObject.class);
if (requestObjectAnn.isPresent()) {
dynamicObjectAnn = Optional.of(
new FGADynamicObject.Literal(REQUEST, requestObjectAnn.get().property(),
requestObjectAnn.get().type()));
}
}
return new Annotations(dynamicObjectAnn, constantObjectAnn, relationAllowedAnn, userTypeAnn);
});
}
Optional findAnnotation(ResourceInfo resourceInfo, Class annotationType) {
return findAnnotation(resourceInfo.getResourceMethod(), annotationType)
.or(() -> findAnnotation(resourceInfo.getResourceClass(), annotationType));
}
Optional findAnnotation(Method method, Class annotationType) {
return annotationQueryCache.computeIfAbsent(new AnnotationQuery(method, annotationType),
query -> searchHierarchyForAnnotation(method, annotationType).map(Function.identity()))
.map(annotationType::cast);
}
Optional searchHierarchyForAnnotation(Method method, Class annotationType) {
return ofNullable(method.getAnnotation(annotationType))
.or(() -> {
var declClass = method.getDeclaringClass();
// Check superclass
try {
var superMethod = declClass.getSuperclass().getDeclaredMethod(method.getName(),
method.getParameterTypes());
var ann = searchHierarchyForAnnotation(superMethod, annotationType);
if (ann.isPresent()) {
return ann;
}
} catch (NullPointerException | NoSuchMethodException e) {
// Ignore
}
// Check interfaces
for (var iface : declClass.getInterfaces()) {
try {
var ifaceMethod = iface.getDeclaredMethod(method.getName(), method.getParameterTypes());
var ann = searchHierarchyForAnnotation(ifaceMethod, annotationType);
if (ann.isPresent()) {
return ann;
}
} catch (NoSuchMethodException e) {
// Ignore
}
}
return empty();
});
}
Optional findAnnotation(Class> cls, Class annotationType) {
return annotationQueryCache.computeIfAbsent(new AnnotationQuery(cls, annotationType),
key -> searchHierarchyForAnnotation(cls, annotationType).map(Function.identity()))
.map(annotationType::cast);
}
Optional searchHierarchyForAnnotation(Class> cls, Class annotationType) {
// Class superclasses handled by @Inherited
return ofNullable(cls.getAnnotation(annotationType))
.or(() -> {
// Check interfaces
for (var iface : cls.getInterfaces()) {
var ann = searchHierarchyForAnnotation(iface, annotationType);
if (ann.isPresent()) {
return ann;
}
}
return empty();
});
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy