diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/AbstractCustomExceptionMapperTest.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/AbstractCustomExceptionMapperTest.java new file mode 100644 index 0000000000000..55ef288f5c5da --- /dev/null +++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/AbstractCustomExceptionMapperTest.java @@ -0,0 +1,164 @@ +package io.quarkus.resteasy.test.security; + +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + +import org.hamcrest.Matchers; +import org.jboss.resteasy.spi.UnhandledException; +import org.junit.jupiter.api.Test; + +import io.quarkus.security.identity.AuthenticationRequestContext; +import io.quarkus.security.identity.IdentityProvider; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.identity.SecurityIdentityAugmentor; +import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest; +import io.quarkus.security.runtime.QuarkusPrincipal; +import io.quarkus.security.runtime.QuarkusSecurityIdentity; +import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils; +import io.restassured.RestAssured; +import io.smallrye.mutiny.Uni; +import io.vertx.ext.web.RoutingContext; + +/** + * Tests internal server errors and other custom exceptions raised during + * proactive authentication can be handled by the exception mappers. + * For lazy authentication, it is important that these exceptions raised during authentication + * required by HTTP permissions are also propagated. + */ +public abstract class AbstractCustomExceptionMapperTest { + + @Test + public void testNoExceptions() { + RestAssured.given() + .auth().preemptive().basic("gaston", "gaston-password") + .get("/hello") + .then() + .statusCode(200) + .body(Matchers.is("Hello Gaston")); + RestAssured.given() + .get("/hello") + .then() + .statusCode(401); + } + + @Test + public void testUnhandledRuntimeException() { + // UnhandledRuntimeException has no exception mapper therefore RESTEasy would wrap it in UnhandledException + // if we started RESTEasy even though there is no matching exception mapper + RestAssured.given() + .auth().preemptive().basic("gaston", "gaston-password") + .header("fail-unhandled", "true") + .get("/hello") + .then() + .statusCode(500) + .body(Matchers.not(Matchers.is(UnhandledException.class.getName()))) + .body(Matchers.containsString(UnhandledRuntimeException.class.getName())) + .body(Matchers.containsString("Expected unhandled failure")); + } + + @Test + public void testCustomExceptionInIdentityProvider() { + RestAssured.given() + .auth().preemptive().basic("gaston", "gaston-password") + .header("fail-authentication", "true") + .get("/hello") + .then() + .statusCode(500) + .body(Matchers.is("Expected authentication failure")); + } + + @Test + public void testCustomExceptionInIdentityAugmentor() { + RestAssured.given() + .auth().preemptive().basic("gaston", "gaston-password") + .header("fail-augmentation", "true") + .get("/hello") + .then() + .statusCode(500) + .body(Matchers.is("Expected identity augmentation failure")); + } + + @Path("/hello") + public static class HelloResource { + @GET + public String hello(@Context SecurityContext context) { + var principalName = context.getUserPrincipal() == null ? "" : " " + context.getUserPrincipal().getName(); + return "Hello" + principalName; + } + + } + + @Provider + public static class CustomRuntimeExceptionMapper implements ExceptionMapper { + @Override + public Response toResponse(CustomRuntimeException exception) { + return Response.serverError().entity(exception.getMessage()).build(); + } + } + + @ApplicationScoped + public static class CustomIdentityAugmentor implements SecurityIdentityAugmentor { + @Override + public Uni augment(SecurityIdentity securityIdentity, + AuthenticationRequestContext authenticationRequestContext) { + return augment(securityIdentity, authenticationRequestContext, Map.of()); + } + + @Override + public Uni augment(SecurityIdentity identity, AuthenticationRequestContext context, + Map attributes) { + final RoutingContext routingContext = HttpSecurityUtils.getRoutingContextAttribute(attributes); + if (routingContext.request().headers().contains("fail-augmentation")) { + return Uni.createFrom().failure(new CustomRuntimeException("Expected identity augmentation failure")); + } + return Uni.createFrom().item(identity); + } + } + + public static class CustomRuntimeException extends RuntimeException { + public CustomRuntimeException(String message) { + super(message); + } + } + + public static class UnhandledRuntimeException extends RuntimeException { + public UnhandledRuntimeException(String message) { + super(message); + } + } + + @ApplicationScoped + public static class BasicIdentityProvider implements IdentityProvider { + + @Override + public Class getRequestType() { + return UsernamePasswordAuthenticationRequest.class; + } + + @Override + public Uni authenticate(UsernamePasswordAuthenticationRequest authRequest, + AuthenticationRequestContext authRequestCtx) { + if (!"gaston".equals(authRequest.getUsername())) { + return Uni.createFrom().nullItem(); + } + + final RoutingContext routingContext = HttpSecurityUtils.getRoutingContextAttribute(authRequest); + if (routingContext.request().headers().contains("fail-authentication")) { + return Uni.createFrom().failure(new CustomRuntimeException("Expected authentication failure")); + } + if (routingContext.request().headers().contains("fail-unhandled")) { + return Uni.createFrom().failure(new UnhandledRuntimeException("Expected unhandled failure")); + } + return Uni.createFrom() + .item(QuarkusSecurityIdentity.builder().setPrincipal(new QuarkusPrincipal("Gaston")).build()); + } + } +} diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/LazyAuthCustomExceptionMapperTest.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/LazyAuthCustomExceptionMapperTest.java new file mode 100644 index 0000000000000..5085343e29649 --- /dev/null +++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/LazyAuthCustomExceptionMapperTest.java @@ -0,0 +1,18 @@ +package io.quarkus.resteasy.test.security; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class LazyAuthCustomExceptionMapperTest extends AbstractCustomExceptionMapperTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest().withApplicationRoot(jar -> jar + .addAsResource(new StringAsset(""" + quarkus.http.auth.permission.authentication.paths=* + quarkus.http.auth.permission.authentication.policy=authenticated + quarkus.http.auth.proactive=false + """), "application.properties")); + +} diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/ProactiveAuthCustomExceptionMapperTest.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/ProactiveAuthCustomExceptionMapperTest.java new file mode 100644 index 0000000000000..c4bd59e5e8a31 --- /dev/null +++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/ProactiveAuthCustomExceptionMapperTest.java @@ -0,0 +1,17 @@ +package io.quarkus.resteasy.test.security; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class ProactiveAuthCustomExceptionMapperTest extends AbstractCustomExceptionMapperTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest().withApplicationRoot(jar -> jar + .addAsResource(new StringAsset(""" + quarkus.http.auth.permission.authentication.paths=* + quarkus.http.auth.permission.authentication.policy=authenticated + """), "application.properties")); + +} diff --git a/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/ResteasyStandaloneRecorder.java b/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/ResteasyStandaloneRecorder.java index 9de7cb651a3b1..8511466a0500c 100644 --- a/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/ResteasyStandaloneRecorder.java +++ b/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/ResteasyStandaloneRecorder.java @@ -1,6 +1,9 @@ package io.quarkus.resteasy.runtime.standalone; import static io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder.DefaultAuthFailureHandler.extractRootCause; +import static io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder.DefaultAuthFailureHandler.isOtherAuthenticationFailure; +import static io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder.DefaultAuthFailureHandler.markIfOtherAuthenticationFailure; +import static io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder.DefaultAuthFailureHandler.removeMarkAsOtherAuthenticationFailure; import java.lang.annotation.Annotation; import java.lang.reflect.Proxy; @@ -37,6 +40,7 @@ import io.quarkus.security.AuthenticationFailedException; import io.quarkus.security.AuthenticationRedirectException; import io.quarkus.security.ForbiddenException; +import io.quarkus.security.UnauthorizedException; import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.quarkus.vertx.http.runtime.HttpCompressionHandler; import io.quarkus.vertx.http.runtime.HttpConfiguration; @@ -163,8 +167,16 @@ public void handle(RoutingContext request) { } } - if (request.failure() instanceof AuthenticationException - || request.failure() instanceof ForbiddenException) { + final Throwable failure = request.failure(); + final boolean isOtherAuthFailure = isOtherAuthenticationFailure(request) + && isFailureHandledByExceptionMappers(failure); + if (isOtherAuthFailure) { + // prevent circular reference for unhandled exceptions + // (which is unnecessary if everything here is done right) + removeMarkAsOtherAuthenticationFailure(request); + super.handle(request); + } else if (failure instanceof AuthenticationException || failure instanceof UnauthorizedException + || failure instanceof ForbiddenException) { super.handle(request); } else { request.next(); @@ -179,6 +191,11 @@ protected void setCurrentIdentityAssociation(RoutingContext routingContext) { } } + private boolean isFailureHandledByExceptionMappers(Throwable failure) { + return failure != null && deployment != null + && deployment.getProviderFactory().getExceptionMapper(failure.getClass()) != null; + } + public Handler defaultAuthFailureHandler() { return new Handler() { @Override @@ -204,6 +221,7 @@ public void handle(RoutingContext event) { @Override public void accept(RoutingContext event, Throwable throwable) { + markIfOtherAuthenticationFailure(event, throwable); if (!event.failed()) { event.fail(extractRootCause(throwable)); } diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/AbstractCustomExceptionMapperTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/AbstractCustomExceptionMapperTest.java new file mode 100644 index 0000000000000..a553cd1823209 --- /dev/null +++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/AbstractCustomExceptionMapperTest.java @@ -0,0 +1,157 @@ +package io.quarkus.resteasy.reactive.server.test.security; + +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.SecurityContext; + +import org.hamcrest.Matchers; +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; +import org.junit.jupiter.api.Test; + +import io.quarkus.security.identity.AuthenticationRequestContext; +import io.quarkus.security.identity.IdentityProvider; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.identity.SecurityIdentityAugmentor; +import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest; +import io.quarkus.security.runtime.QuarkusPrincipal; +import io.quarkus.security.runtime.QuarkusSecurityIdentity; +import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils; +import io.restassured.RestAssured; +import io.smallrye.mutiny.Uni; +import io.vertx.ext.web.RoutingContext; + +/** + * Tests internal server errors and other custom exceptions raised during + * proactive authentication can be handled by the exception mappers. + * For lazy authentication, it is important that these exceptions raised during authentication + * required by HTTP permissions are also propagated. + */ +public abstract class AbstractCustomExceptionMapperTest { + + @Test + public void testNoExceptions() { + RestAssured.given() + .auth().preemptive().basic("gaston", "gaston-password") + .get("/hello") + .then() + .statusCode(200) + .body(Matchers.is("Hello Gaston")); + RestAssured.given() + .get("/hello") + .then() + .statusCode(401); + } + + @Test + public void testUnhandledRuntimeException() { + RestAssured.given() + .auth().preemptive().basic("gaston", "gaston-password") + .header("fail-unhandled", "true") + .get("/hello") + .then() + .statusCode(500) + .body(Matchers.containsString(UnhandledRuntimeException.class.getName())) + .body(Matchers.containsString("Expected unhandled failure")); + } + + @Test + public void testCustomExceptionInIdentityProvider() { + RestAssured.given() + .auth().preemptive().basic("gaston", "gaston-password") + .header("fail-authentication", "true") + .get("/hello") + .then() + .statusCode(500) + .body(Matchers.is("Expected authentication failure")); + } + + @Test + public void testCustomExceptionInIdentityAugmentor() { + RestAssured.given() + .auth().preemptive().basic("gaston", "gaston-password") + .header("fail-augmentation", "true") + .get("/hello") + .then() + .statusCode(500) + .body(Matchers.is("Expected identity augmentation failure")); + } + + @Path("/hello") + public static class HelloResource { + @GET + public String hello(@Context SecurityContext context) { + var principalName = context.getUserPrincipal() == null ? "" : " " + context.getUserPrincipal().getName(); + return "Hello" + principalName; + } + } + + public static class Mappers { + @ServerExceptionMapper(CustomRuntimeException.class) + public Response toResponse(CustomRuntimeException exception) { + return Response.serverError().entity(exception.getMessage()).build(); + } + } + + @ApplicationScoped + public static class CustomIdentityAugmentor implements SecurityIdentityAugmentor { + @Override + public Uni augment(SecurityIdentity securityIdentity, + AuthenticationRequestContext authenticationRequestContext) { + return augment(securityIdentity, authenticationRequestContext, Map.of()); + } + + @Override + public Uni augment(SecurityIdentity identity, AuthenticationRequestContext context, + Map attributes) { + final RoutingContext routingContext = HttpSecurityUtils.getRoutingContextAttribute(attributes); + if (routingContext.request().headers().contains("fail-augmentation")) { + return Uni.createFrom().failure(new CustomRuntimeException("Expected identity augmentation failure")); + } + return Uni.createFrom().item(identity); + } + } + + public static class CustomRuntimeException extends RuntimeException { + public CustomRuntimeException(String message) { + super(message); + } + } + + public static class UnhandledRuntimeException extends RuntimeException { + public UnhandledRuntimeException(String message) { + super(message); + } + } + + @ApplicationScoped + public static class BasicIdentityProvider implements IdentityProvider { + + @Override + public Class getRequestType() { + return UsernamePasswordAuthenticationRequest.class; + } + + @Override + public Uni authenticate(UsernamePasswordAuthenticationRequest authRequest, + AuthenticationRequestContext authRequestCtx) { + if (!"gaston".equals(authRequest.getUsername())) { + return Uni.createFrom().nullItem(); + } + + final RoutingContext routingContext = HttpSecurityUtils.getRoutingContextAttribute(authRequest); + if (routingContext.request().headers().contains("fail-authentication")) { + return Uni.createFrom().failure(new CustomRuntimeException("Expected authentication failure")); + } + if (routingContext.request().headers().contains("fail-unhandled")) { + return Uni.createFrom().failure(new UnhandledRuntimeException("Expected unhandled failure")); + } + return Uni.createFrom() + .item(QuarkusSecurityIdentity.builder().setPrincipal(new QuarkusPrincipal("Gaston")).build()); + } + } +} diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/LazyAuthCustomExceptionMapperTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/LazyAuthCustomExceptionMapperTest.java new file mode 100644 index 0000000000000..c57a3eeabbbe9 --- /dev/null +++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/LazyAuthCustomExceptionMapperTest.java @@ -0,0 +1,18 @@ +package io.quarkus.resteasy.reactive.server.test.security; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class LazyAuthCustomExceptionMapperTest extends AbstractCustomExceptionMapperTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest().withApplicationRoot(jar -> jar + .addAsResource(new StringAsset(""" + quarkus.http.auth.permission.authentication.paths=* + quarkus.http.auth.permission.authentication.policy=authenticated + quarkus.http.auth.proactive=false + """), "application.properties")); + +} diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/ProactiveAuthCustomExceptionMapperTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/ProactiveAuthCustomExceptionMapperTest.java new file mode 100644 index 0000000000000..afe82d74700a0 --- /dev/null +++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/ProactiveAuthCustomExceptionMapperTest.java @@ -0,0 +1,17 @@ +package io.quarkus.resteasy.reactive.server.test.security; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class ProactiveAuthCustomExceptionMapperTest extends AbstractCustomExceptionMapperTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest().withApplicationRoot(jar -> jar + .addAsResource(new StringAsset(""" + quarkus.http.auth.permission.authentication.paths=* + quarkus.http.auth.permission.authentication.policy=authenticated + """), "application.properties")); + +} diff --git a/extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRecorder.java b/extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRecorder.java index 0d28c5159b7ed..9b438d2452ccd 100644 --- a/extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRecorder.java +++ b/extensions/resteasy-reactive/rest/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRecorder.java @@ -1,6 +1,9 @@ package io.quarkus.resteasy.reactive.server.runtime; import static io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder.DefaultAuthFailureHandler.extractRootCause; +import static io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder.DefaultAuthFailureHandler.isOtherAuthenticationFailure; +import static io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder.DefaultAuthFailureHandler.markIfOtherAuthenticationFailure; +import static io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder.DefaultAuthFailureHandler.removeMarkAsOtherAuthenticationFailure; import java.io.Closeable; import java.lang.reflect.InvocationTargetException; @@ -66,6 +69,7 @@ import io.quarkus.security.AuthenticationFailedException; import io.quarkus.security.AuthenticationRedirectException; import io.quarkus.security.ForbiddenException; +import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.CurrentIdentityAssociation; import io.quarkus.vertx.http.runtime.CurrentVertxRequest; import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; @@ -231,9 +235,15 @@ public void handle(RoutingContext event) { } } - if (event.failure() instanceof AuthenticationException - || event.failure() instanceof ForbiddenException) { - restInitialHandler.beginProcessing(event, event.failure()); + final Throwable failure = event.failure(); + final boolean isOtherAuthFailure = isOtherAuthenticationFailure(event) + && isFailureHandledByExceptionMappers(failure); + if (isOtherAuthFailure) { + removeMarkAsOtherAuthenticationFailure(event); + restInitialHandler.beginProcessing(event, failure); + } else if (failure instanceof AuthenticationException + || failure instanceof UnauthorizedException || failure instanceof ForbiddenException) { + restInitialHandler.beginProcessing(event, failure); } else { event.next(); } @@ -241,6 +251,11 @@ public void handle(RoutingContext event) { }; } + private boolean isFailureHandledByExceptionMappers(Throwable throwable) { + return currentDeployment != null + && currentDeployment.getExceptionMapper().getExceptionMapper(throwable.getClass(), null, null) != null; + } + /** * This is Quarkus specific. *

@@ -420,6 +435,7 @@ private static final class FailingDefaultAuthFailureHandler implements BiConsume @Override public void accept(RoutingContext event, Throwable throwable) { + markIfOtherAuthenticationFailure(event, throwable); if (!event.failed()) { event.fail(extractRootCause(throwable)); } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java index 3ec1ae277341f..1f1faee90d42d 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java @@ -33,6 +33,7 @@ import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.security.AuthenticationCompletionException; +import io.quarkus.security.AuthenticationException; import io.quarkus.security.AuthenticationFailedException; import io.quarkus.security.AuthenticationRedirectException; import io.quarkus.security.identity.SecurityIdentity; @@ -171,6 +172,12 @@ public Map get() { public static abstract class DefaultAuthFailureHandler implements BiConsumer { + /** + * A {@link RoutingContext#get(String)} key added for exceptions raised during authentication that are not + * the {@link io.quarkus.security.AuthenticationException}. + */ + private static final String OTHER_AUTHENTICATION_FAILURE = "io.quarkus.vertx.http.runtime.security.other-auth-failure"; + protected DefaultAuthFailureHandler() { } @@ -206,6 +213,7 @@ public void accept(Throwable throwable) { event.response().headers().set("Pragma", "no-cache"); proceed(throwable); } else { + event.put(OTHER_AUTHENTICATION_FAILURE, Boolean.TRUE); event.fail(throwable); } } @@ -227,6 +235,20 @@ public static Throwable extractRootCause(Throwable throwable) { } return throwable; } + + public static void markIfOtherAuthenticationFailure(RoutingContext event, Throwable throwable) { + if (!(throwable instanceof AuthenticationException)) { + event.put(OTHER_AUTHENTICATION_FAILURE, Boolean.TRUE); + } + } + + public static void removeMarkAsOtherAuthenticationFailure(RoutingContext event) { + event.remove(OTHER_AUTHENTICATION_FAILURE); + } + + public static boolean isOtherAuthenticationFailure(RoutingContext event) { + return Boolean.TRUE.equals(event.get(OTHER_AUTHENTICATION_FAILURE)); + } } public static final class AuthenticationHandler implements Handler {