Skip to content

Commit

Permalink
RESTEasy Reactive - prevent repeating of standard security checks
Browse files Browse the repository at this point in the history
  • Loading branch information
michalvavrik committed Jul 5, 2022
1 parent 64f47d0 commit d0b11f5
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder;
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRuntimeRecorder;
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveServerRuntimeConfig;
import io.quarkus.resteasy.reactive.server.runtime.StandardSecurityCheckInterceptor;
import io.quarkus.resteasy.reactive.server.runtime.exceptionmappers.AuthenticationCompletionExceptionMapper;
import io.quarkus.resteasy.reactive.server.runtime.exceptionmappers.AuthenticationFailedExceptionMapper;
import io.quarkus.resteasy.reactive.server.runtime.exceptionmappers.AuthenticationRedirectExceptionMapper;
Expand Down Expand Up @@ -1044,6 +1045,17 @@ public List<HandlerChainCustomizer> scan(MethodInfo method, ClassInfo actualEndp
});
}

@BuildStep
void registerSecurityInterceptors(Capabilities capabilities,
BuildProducer<AdditionalBeanBuildItem> beans) {
if (capabilities.isPresent(Capability.SECURITY)) {
// Register interceptors for standard security annotations to prevent repeated security checks
beans.produce(new AdditionalBeanBuildItem(StandardSecurityCheckInterceptor.RolesAllowedInterceptor.class,
StandardSecurityCheckInterceptor.AuthenticatedInterceptor.class,
StandardSecurityCheckInterceptor.PermitAllInterceptor.class));
}
}

private <T> T consumeStandardSecurityAnnotations(MethodInfo methodInfo, ClassInfo classInfo, IndexView index,
Function<ClassInfo, T> function) {
if (SecurityTransformerUtils.hasStandardSecurityAnnotation(methodInfo)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.quarkus.resteasy.reactive.server.runtime;

import static io.quarkus.security.spi.runtime.SecurityHandlerConstants.EXECUTED;
import static io.quarkus.security.spi.runtime.SecurityHandlerConstants.SECURITY_HANDLER;

import javax.annotation.Priority;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

import org.jboss.resteasy.reactive.server.core.CurrentRequestManager;

import io.quarkus.security.Authenticated;
import io.quarkus.security.spi.runtime.AuthorizationController;

/**
* Security checks for RBAC annotations on endpoints are done by
* the {@link io.quarkus.resteasy.reactive.server.runtime.security.EagerSecurityHandler},
* this interceptor propagates the information to the SecurityHandler to prevent repeated checks. The {@link DenyAll}
* security check is performed just once.
*/
public abstract class StandardSecurityCheckInterceptor {

@Inject
AuthorizationController controller;

@AroundInvoke
public Object intercept(InvocationContext ic) throws Exception {
if (controller.isAuthorizationEnabled()
&& alreadyDoneByEagerSecurityHandler(CurrentRequestManager.get().getProperty(SECURITY_HANDLER))) {
ic.getContextData().put(SECURITY_HANDLER, EXECUTED);
}
return ic.proceed();
}

private boolean alreadyDoneByEagerSecurityHandler(Object property) {
return property != null && property.equals(EXECUTED);
}

/**
* Prevent the SecurityHandler from performing {@link RolesAllowed} security checks
*/
@Interceptor
@RolesAllowed("")
@Priority(Interceptor.Priority.PLATFORM_BEFORE)
public static final class RolesAllowedInterceptor extends StandardSecurityCheckInterceptor {

}

/**
* Prevent the SecurityHandler from performing {@link javax.annotation.security.PermitAll} security checks
*/
@Interceptor
@PermitAll
@Priority(Interceptor.Priority.PLATFORM_BEFORE)
public static final class PermitAllInterceptor extends StandardSecurityCheckInterceptor {

}

/**
* Prevent the SecurityHandler from performing {@link Authenticated} security checks
*/
@Interceptor
@Authenticated
@Priority(Interceptor.Priority.PLATFORM_BEFORE)
public static final class AuthenticatedInterceptor extends StandardSecurityCheckInterceptor {

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.quarkus.resteasy.reactive.server.runtime.security;

import static io.quarkus.security.spi.runtime.SecurityHandlerConstants.EXECUTED;
import static io.quarkus.security.spi.runtime.SecurityHandlerConstants.SECURITY_HANDLER;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -75,7 +78,9 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti

requestContext.requireCDIRequestScope();
SecurityCheck theCheck = check;
if (!theCheck.isPermitAll()) {
if (theCheck.isPermitAll()) {
preventRepeatedSecurityChecks(requestContext);
} else {
requestContext.suspend();
Uni<SecurityIdentity> deferredIdentity = getCurrentIdentityAssociation().get().getDeferredIdentity();

Expand All @@ -95,6 +100,7 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti
public Object apply(SecurityIdentity securityIdentity) {
theCheck.apply(securityIdentity, methodDescription,
requestContext.getParameters());
preventRepeatedSecurityChecks(requestContext);
return null;
}
})
Expand All @@ -117,6 +123,12 @@ public void onFailure(Throwable failure) {
}
}

private void preventRepeatedSecurityChecks(ResteasyReactiveRequestContext requestContext) {
// propagate information that security check has been performed to the SecurityHandler
// via io.quarkus.resteasy.reactive.server.runtime.StandardSecurityCheckInterceptor
requestContext.setProperty(SECURITY_HANDLER, EXECUTED);
}

private InjectableInstance<CurrentIdentityAssociation> getCurrentIdentityAssociation() {
InjectableInstance<CurrentIdentityAssociation> identityAssociation = this.currentIdentityAssociation;
if (identityAssociation == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.quarkus.security.spi.runtime;

public class SecurityHandlerConstants {

/**
* Invocation context data key used by the SecurityHandler to save a security checks state
*/
public static final String SECURITY_HANDLER = "SECURITY_HANDLER";

/**
* The SecurityHandler keep a state of security checks in the Invocation context data to prevent repeated checks.
* `executed` means the check has already been done.
*/
public static final String EXECUTED = "executed";
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.quarkus.security.runtime.interceptor;

import static io.quarkus.security.spi.runtime.SecurityHandlerConstants.EXECUTED;
import static io.quarkus.security.spi.runtime.SecurityHandlerConstants.SECURITY_HANDLER;

import java.util.concurrent.CompletionStage;
import java.util.function.Function;

Expand All @@ -16,9 +19,6 @@
@Singleton
public class SecurityHandler {

private static final String HANDLER_NAME = SecurityHandler.class.getName();
private static final String EXECUTED = "executed";

@Inject
SecurityConstrainer constrainer;

Expand Down Expand Up @@ -49,7 +49,7 @@ public Object handle(InvocationContext ic) throws Exception {
}

private boolean alreadyHandled(InvocationContext ic) {
return ic.getContextData().put(HANDLER_NAME, EXECUTED) != null;
return ic.getContextData().put(SECURITY_HANDLER, EXECUTED) != null;
}

private static class UniContinuation implements Function<Object, Uni<?>> {
Expand Down

0 comments on commit d0b11f5

Please sign in to comment.