From e3f514e92d353de731637c5ff10574856586544b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Sat, 4 May 2024 14:10:27 +0200 Subject: [PATCH] Build SecurityEventHelper lazily due to static interceptors --- .../RolesAllowedExpressionTest.java | 22 ++++++- .../spi/runtime/SecurityEventHelper.java | 59 +++++++++++++++++++ .../interceptor/SecurityConstrainer.java | 8 +-- 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/extensions/security/deployment/src/test/java/io/quarkus/security/test/rolesallowed/RolesAllowedExpressionTest.java b/extensions/security/deployment/src/test/java/io/quarkus/security/test/rolesallowed/RolesAllowedExpressionTest.java index f29bc1f0c6f4eb..bb63fb075ba5b5 100644 --- a/extensions/security/deployment/src/test/java/io/quarkus/security/test/rolesallowed/RolesAllowedExpressionTest.java +++ b/extensions/security/deployment/src/test/java/io/quarkus/security/test/rolesallowed/RolesAllowedExpressionTest.java @@ -12,6 +12,7 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; +import org.eclipse.microprofile.config.ConfigProvider; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -47,7 +48,7 @@ public class RolesAllowedExpressionTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addClasses(RolesAllowedBean.class, IdentityMock.class, - AuthData.class, SecurityTestUtils.class) + AuthData.class, SecurityTestUtils.class, SecuredUtils.class) .addAsResource(new StringAsset(APP_PROPS), "application.properties")); @Inject @@ -97,6 +98,12 @@ public void shouldRestrictAccessToSpecificRole() { new AuthData(Set.of("cn=Administrator,ou=Software,dc=Tester,dc=User"), false, "ldap")); } + @Test + public void testStaticSecuredMethod() { + assertSuccess(SecuredUtils::staticSecuredMethod, "admin", ADMIN); + assertFailureFor(SecuredUtils::staticSecuredMethod, ForbiddenException.class, USER); + } + @Singleton public static class RolesAllowedBean { @@ -153,4 +160,17 @@ public final String ldap() { } + public static class SecuredUtils { + + private SecuredUtils() { + // UTIL CLASS + } + + @RolesAllowed("${sudo}") + public static String staticSecuredMethod() { + return ConfigProvider.getConfig().getValue("sudo", String.class); + } + + } + } diff --git a/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityEventHelper.java b/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityEventHelper.java index e9a70973b56611..8907c4e5418948 100644 --- a/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityEventHelper.java +++ b/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityEventHelper.java @@ -5,6 +5,8 @@ import jakarta.enterprise.event.Event; import jakarta.enterprise.inject.spi.BeanManager; +import org.eclipse.microprofile.config.ConfigProvider; + import io.quarkus.security.identity.SecurityIdentity; public class SecurityEventHelper { @@ -90,4 +92,61 @@ public Map getEventProperties() { return false; } } + + /** + * Creates {@link SecurityEventHelper} initialized on first request. + * This method should only be used when there is a risk the helper will be initialized during the static init phase. + * During the runtime init phase, prefer the constructor. + */ + public static SecurityEventHelper lazilyOf(Event successEvent, + Event failureEvent, S successInstance, F failureInstance, BeanManager beanManager) { + return new SecurityEventHelper<>(successEvent, failureEvent, successInstance, failureInstance, beanManager, true) { + + private volatile Boolean eventsDisabled = null; + + private boolean areEventsDisabled() { + if (eventsDisabled == null) { + synchronized (this) { + if (eventsDisabled == null) { + this.eventsDisabled = !ConfigProvider.getConfig().getValue("quarkus.security.events.enabled", + Boolean.class); + } + } + } + return eventsDisabled; + } + + @Override + public void fireSuccessEvent(S successInstance) { + if (areEventsDisabled()) { + return; + } + super.fireSuccessEvent(successInstance); + } + + @Override + public void fireFailureEvent(F failureInstance) { + if (areEventsDisabled()) { + return; + } + super.fireFailureEvent(failureInstance); + } + + @Override + public boolean fireEventOnSuccess() { + if (areEventsDisabled()) { + return false; + } + return super.fireEventOnSuccess(); + } + + @Override + public boolean fireEventOnFailure() { + if (areEventsDisabled()) { + return false; + } + return super.fireEventOnFailure(); + } + }; + } } diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java index 3d1f357ad9b774..a8d68b3c23e48b 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java @@ -14,7 +14,6 @@ import io.quarkus.runtime.BlockingOperationNotAllowedException; import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.security.runtime.SecurityConfig; import io.quarkus.security.runtime.SecurityIdentityAssociation; import io.quarkus.security.spi.runtime.AuthorizationFailureEvent; import io.quarkus.security.spi.runtime.AuthorizationSuccessEvent; @@ -36,11 +35,12 @@ public class SecurityConstrainer { @Inject SecurityIdentityAssociation identityAssociation; - SecurityConstrainer(SecurityCheckStorage storage, BeanManager beanManager, SecurityConfig securityConfig, + SecurityConstrainer(SecurityCheckStorage storage, BeanManager beanManager, Event authZFailureEvent, Event authZSuccessEvent) { this.storage = storage; - this.securityEventHelper = new SecurityEventHelper<>(authZSuccessEvent, authZFailureEvent, AUTHORIZATION_SUCCESS, - AUTHORIZATION_FAILURE, beanManager, securityConfig.events().enabled()); + // static interceptors are initialized during the static init, therefore we need to initialize the helper lazily + this.securityEventHelper = SecurityEventHelper.lazilyOf(authZSuccessEvent, authZFailureEvent, + AUTHORIZATION_SUCCESS, AUTHORIZATION_FAILURE, beanManager); } public void check(Method method, Object[] parameters) {