Skip to content

Commit

Permalink
Don't run CDI interceptors on class-level exception mappers
Browse files Browse the repository at this point in the history
The previous behavior was pretty weird and prevented us
from using @ServerExceptionMapper in classes that were
annotated with security annnotations

The longer discussion is at:
https://quarkusio.zulipchat.com/#narrow/stream/187038-dev/topic/Bean.20scope.20is.20changed.20when.20adding.20a.20.40ServerExceptionMapper.20.3F/near/427889997

Co-authored-by: Ladislav Thon <ladicek@gmail.com>
  • Loading branch information
geoand and Ladicek committed Mar 20, 2024
1 parent f5aed6d commit 966063b
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import org.jboss.jandex.DotName;
import org.jboss.resteasy.reactive.multipart.FileUpload;

import io.quarkus.arc.NoClassInterceptors;

final class DotNames {

static final String POPULATE_METHOD_NAME = "populate";
Expand All @@ -20,6 +22,8 @@ final class DotNames {
static final DotName PATH_NAME = DotName.createSimple(Path.class.getName());
static final DotName FILE_NAME = DotName.createSimple(File.class.getName());

static final DotName NO_CLASS_INTERCEPTORS = DotName.createSimple(NoClassInterceptors.class);

private DotNames() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames;
import org.jboss.resteasy.reactive.common.processor.scanning.ResourceScanningResult;
import org.jboss.resteasy.reactive.server.injection.ContextProducers;
import org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.AutoInjectAnnotationBuildItem;
import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.arc.processor.Annotations;
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.BuiltinScope;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.deployment.annotations.BuildProducer;
Expand Down Expand Up @@ -85,6 +90,39 @@ void unremovableContextMethodParams(Optional<ResourceScanningResultBuildItem> re
}
}

@BuildStep
void perClassExceptionMapperSupport(Optional<ResourceScanningResultBuildItem> resourceScanningResultBuildItem,
BuildProducer<AnnotationsTransformerBuildItem> producer) {
if (resourceScanningResultBuildItem.isEmpty()) {
return;
}
List<MethodInfo> methodExceptionMapper = resourceScanningResultBuildItem.get().getResult()
.getClassLevelExceptionMappers();
if (methodExceptionMapper.isEmpty()) {
return;
}
producer.produce(new AnnotationsTransformerBuildItem(new AnnotationsTransformer() {

@Override
public boolean appliesTo(AnnotationTarget.Kind kind) {
return kind == AnnotationTarget.Kind.METHOD;
}

@Override
public void transform(TransformationContext ctx) {
final Collection<AnnotationInstance> annotations = ctx.getAnnotations();
AnnotationInstance serverExceptionMapper = Annotations.find(annotations,
ResteasyReactiveDotNames.SERVER_EXCEPTION_MAPPER);
if (serverExceptionMapper == null) {
return;
}
// we want to make sure that class level exception mappers do not run
// interceptors bound using class-level interceptor bindings (like security)
ctx.transform().add(DotNames.NO_CLASS_INTERCEPTORS).done();
}
}));
}

@BuildStep
void subResourcesAsBeans(ResourceScanningResultBuildItem setupEndpointsResult,
List<SubResourcesAsBeansBuildItem> subResourcesAsBeans,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.quarkus.resteasy.reactive.server.test.security;

import static io.restassured.RestAssured.when;

import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;

import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.security.UnauthorizedException;
import io.quarkus.test.QuarkusUnitTest;

public class CustomClassLevelExceptionMapperTest {

@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(HelloResource.class));

@Test
public void shouldDenyUnannotated() {
when().get("hello")
.then()
.statusCode(999);
}

@Path("hello")
@RolesAllowed("test")
public static final class HelloResource {

@GET
public String hello() {
return "hello world";
}

@ServerExceptionMapper(UnauthorizedException.class)
public Response forbidden() {
return Response.status(999).build();
}
}
}

0 comments on commit 966063b

Please sign in to comment.