From b08cf2f100a24b7b7225756d558303236f713846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Sat, 11 Nov 2023 01:04:55 +0100 Subject: [PATCH] Move form auth and basic realm to runtime --- .../deployment/HttpSecurityProcessor.java | 27 ++-- .../vertx/http/runtime/AuthConfig.java | 6 - .../vertx/http/runtime/AuthRuntimeConfig.java | 13 ++ .../vertx/http/runtime/FormAuthConfig.java | 110 ---------------- .../http/runtime/FormAuthRuntimeConfig.java | 122 ++++++++++++++++++ .../BasicAuthenticationMechanism.java | 40 ++---- .../security/FormAuthenticationMechanism.java | 26 ++-- .../security/HttpSecurityRecorder.java | 11 -- 8 files changed, 171 insertions(+), 184 deletions(-) create mode 100644 extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthRuntimeConfig.java diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java index fb34fd418ef28..1548509f4c05f 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java @@ -1,6 +1,7 @@ package io.quarkus.vertx.http.deployment; import static io.quarkus.arc.processor.DotNames.APPLICATION_SCOPED; +import static io.quarkus.arc.processor.DotNames.DEFAULT_BEAN; import static io.quarkus.arc.processor.DotNames.SINGLETON; import java.util.List; @@ -11,12 +12,14 @@ import java.util.stream.Collectors; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Singleton; +import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.annotations.BuildProducer; @@ -30,7 +33,6 @@ import io.quarkus.vertx.http.runtime.security.BasicAuthenticationMechanism; import io.quarkus.vertx.http.runtime.security.EagerSecurityInterceptorStorage; import io.quarkus.vertx.http.runtime.security.FormAuthenticationMechanism; -import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism; import io.quarkus.vertx.http.runtime.security.HttpAuthenticator; import io.quarkus.vertx.http.runtime.security.HttpAuthorizer; import io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder; @@ -42,6 +44,8 @@ public class HttpSecurityProcessor { + private static final DotName BASIC_AUTH_MECH_NAME = DotName.createSimple(BasicAuthenticationMechanism.class); + @Record(ExecutionTime.STATIC_INIT) @BuildStep void produceNamedHttpSecurityPolicies(List httpSecurityPolicyBuildItems, @@ -79,26 +83,23 @@ AdditionalBeanBuildItem initMtlsClientAuth(HttpBuildTimeConfig buildTimeConfig) } @BuildStep(onlyIf = IsApplicationBasicAuthRequired.class) - @Record(ExecutionTime.STATIC_INIT) - SyntheticBeanBuildItem initBasicAuth( - HttpSecurityRecorder recorder, - HttpBuildTimeConfig buildTimeConfig, + AdditionalBeanBuildItem initBasicAuth(HttpBuildTimeConfig buildTimeConfig, + BuildProducer annotationsTransformerProducer, BuildProducer securityInformationProducer) { - SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem - .configure(BasicAuthenticationMechanism.class) - .types(HttpAuthenticationMechanism.class) - .scope(Singleton.class) - .supplier(recorder.setupBasicAuth(buildTimeConfig)); + if (!buildTimeConfig.auth.form.enabled && !isMtlsClientAuthenticationEnabled(buildTimeConfig) && !buildTimeConfig.auth.basic.orElse(false)) { //if not explicitly enabled we make this a default bean, so it is the fallback if nothing else is defined - configurator.defaultBean(); + annotationsTransformerProducer.produce(new AnnotationsTransformerBuildItem(AnnotationsTransformer + .appliedToClass() + .whenClass(cl -> BASIC_AUTH_MECH_NAME.equals(cl.name())) + .thenTransform(t -> t.add(DEFAULT_BEAN)))); if (buildTimeConfig.auth.basic.isPresent() && buildTimeConfig.auth.basic.get()) { securityInformationProducer.produce(SecurityInformationBuildItem.BASIC()); } } - return configurator.done(); + return AdditionalBeanBuildItem.builder().setUnremovable().addBeanClass(BasicAuthenticationMechanism.class).build(); } public static boolean applicationBasicAuthRequired(HttpBuildTimeConfig buildTimeConfig, diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthConfig.java index 52b9017a38daa..e4ff3dec29715 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthConfig.java @@ -25,12 +25,6 @@ public class AuthConfig { @ConfigItem public FormAuthConfig form; - /** - * The authentication realm - */ - @ConfigItem - public Optional realm; - /** * If this is true and credentials are present then a user will always be authenticated * before the request progresses. diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthRuntimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthRuntimeConfig.java index eee0b3f84d897..8552e30ef6b6b 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthRuntimeConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthRuntimeConfig.java @@ -1,6 +1,7 @@ package io.quarkus.vertx.http.runtime; import java.util.Map; +import java.util.Optional; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -22,4 +23,16 @@ public class AuthRuntimeConfig { */ @ConfigItem(name = "policy") public Map rolePolicy; + + /** + * The authentication realm + */ + @ConfigItem + public Optional realm; + + /** + * Form Auth config + */ + @ConfigItem + public FormAuthRuntimeConfig form; } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthConfig.java index e08e6595129ba..2e6d38250c8da 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthConfig.java @@ -1,8 +1,5 @@ package io.quarkus.vertx.http.runtime; -import java.time.Duration; -import java.util.Optional; - import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -11,14 +8,6 @@ */ @ConfigGroup public class FormAuthConfig { - /** - * SameSite attribute values for the session and location cookies. - */ - public enum CookieSameSite { - STRICT, - LAX, - NONE - } /** * If form authentication is enabled. @@ -26,109 +15,10 @@ public enum CookieSameSite { @ConfigItem public boolean enabled; - /** - * The login page. Redirect to login page can be disabled by setting `quarkus.http.auth.form.login-page=`. - */ - @ConfigItem(defaultValue = "/login.html") - public Optional loginPage; - /** * The post location. */ @ConfigItem(defaultValue = "/j_security_check") public String postLocation; - /** - * The username field name. - */ - @ConfigItem(defaultValue = "j_username") - public String usernameParameter; - - /** - * The password field name. - */ - @ConfigItem(defaultValue = "j_password") - public String passwordParameter; - - /** - * The error page. Redirect to error page can be disabled by setting `quarkus.http.auth.form.error-page=`. - */ - @ConfigItem(defaultValue = "/error.html") - public Optional errorPage; - - /** - * The landing page to redirect to if there is no saved page to redirect back to. - * Redirect to landing page can be disabled by setting `quarkus.http.auth.form.landing-page=`. - */ - @ConfigItem(defaultValue = "/index.html") - public Optional landingPage; - - /** - * Option to disable redirect to landingPage if there is no saved page to redirect back to. Form Auth POST is followed - * by redirect to landingPage by default. - * - * @deprecated redirect to landingPage can be disabled by removing default landing page - * (via `quarkus.http.auth.form.landing-page=`). Quarkus will ignore this configuration property - * if there is no landing page. - */ - @ConfigItem(defaultValue = "true") - @Deprecated - public boolean redirectAfterLogin; - - /** - * Option to control the name of the cookie used to redirect the user back - * to where he wants to get access to. - */ - @ConfigItem(defaultValue = "quarkus-redirect-location") - public String locationCookie; - - /** - * The inactivity (idle) timeout - * - * When inactivity timeout is reached, cookie is not renewed and a new login is enforced. - */ - @ConfigItem(defaultValue = "PT30M") - public Duration timeout; - - /** - * How old a cookie can get before it will be replaced with a new cookie with an updated timeout, also - * referred to as "renewal-timeout". - * - * Note that smaller values will result in slightly more server load (as new encrypted cookies will be - * generated more often), however larger values affect the inactivity timeout as the timeout is set - * when a cookie is generated. - * - * For example if this is set to 10 minutes, and the inactivity timeout is 30m, if a users last request - * is when the cookie is 9m old then the actual timeout will happen 21m after the last request, as the timeout - * is only refreshed when a new cookie is generated. - * - * In other words no timeout is tracked on the server side; the timestamp is encoded and encrypted in the cookie itself, - * and it is decrypted and parsed with each request. - */ - @ConfigItem(defaultValue = "PT1M") - public Duration newCookieInterval; - - /** - * The cookie that is used to store the persistent session - */ - @ConfigItem(defaultValue = "quarkus-credential") - public String cookieName; - - /** - * The cookie path for the session and location cookies. - */ - @ConfigItem(defaultValue = "/") - public Optional cookiePath = Optional.of("/"); - - /** - * Set the HttpOnly attribute to prevent access to the cookie via JavaScript. - */ - @ConfigItem(defaultValue = "false") - public boolean httpOnlyCookie; - - /** - * SameSite attribute for the session and location cookies. - */ - @ConfigItem(defaultValue = "strict") - public CookieSameSite cookieSameSite = CookieSameSite.STRICT; } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthRuntimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthRuntimeConfig.java new file mode 100644 index 0000000000000..d4152fe4a9b13 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthRuntimeConfig.java @@ -0,0 +1,122 @@ +package io.quarkus.vertx.http.runtime; + +import java.time.Duration; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +/** + * config for the form authentication mechanism + */ +@ConfigGroup +public class FormAuthRuntimeConfig { + /** + * SameSite attribute values for the session and location cookies. + */ + public enum CookieSameSite { + STRICT, + LAX, + NONE + } + + /** + * The login page. Redirect to login page can be disabled by setting `quarkus.http.auth.form.login-page=`. + */ + @ConfigItem(defaultValue = "/login.html") + public Optional loginPage; + + /** + * The username field name. + */ + @ConfigItem(defaultValue = "j_username") + public String usernameParameter; + + /** + * The password field name. + */ + @ConfigItem(defaultValue = "j_password") + public String passwordParameter; + + /** + * The error page. Redirect to error page can be disabled by setting `quarkus.http.auth.form.error-page=`. + */ + @ConfigItem(defaultValue = "/error.html") + public Optional errorPage; + + /** + * The landing page to redirect to if there is no saved page to redirect back to. + * Redirect to landing page can be disabled by setting `quarkus.http.auth.form.landing-page=`. + */ + @ConfigItem(defaultValue = "/index.html") + public Optional landingPage; + + /** + * Option to disable redirect to landingPage if there is no saved page to redirect back to. Form Auth POST is followed + * by redirect to landingPage by default. + * + * @deprecated redirect to landingPage can be disabled by removing default landing page + * (via `quarkus.http.auth.form.landing-page=`). Quarkus will ignore this configuration property + * if there is no landing page. + */ + @ConfigItem(defaultValue = "true") + @Deprecated + public boolean redirectAfterLogin; + + /** + * Option to control the name of the cookie used to redirect the user back + * to where he wants to get access to. + */ + @ConfigItem(defaultValue = "quarkus-redirect-location") + public String locationCookie; + + /** + * The inactivity (idle) timeout + * + * When inactivity timeout is reached, cookie is not renewed and a new login is enforced. + */ + @ConfigItem(defaultValue = "PT30M") + public Duration timeout; + + /** + * How old a cookie can get before it will be replaced with a new cookie with an updated timeout, also + * referred to as "renewal-timeout". + * + * Note that smaller values will result in slightly more server load (as new encrypted cookies will be + * generated more often), however larger values affect the inactivity timeout as the timeout is set + * when a cookie is generated. + * + * For example if this is set to 10 minutes, and the inactivity timeout is 30m, if a users last request + * is when the cookie is 9m old then the actual timeout will happen 21m after the last request, as the timeout + * is only refreshed when a new cookie is generated. + * + * In other words no timeout is tracked on the server side; the timestamp is encoded and encrypted in the cookie itself, + * and it is decrypted and parsed with each request. + */ + @ConfigItem(defaultValue = "PT1M") + public Duration newCookieInterval; + + /** + * The cookie that is used to store the persistent session + */ + @ConfigItem(defaultValue = "quarkus-credential") + public String cookieName; + + /** + * The cookie path for the session and location cookies. + */ + @ConfigItem(defaultValue = "/") + public Optional cookiePath = Optional.of("/"); + + /** + * Set the HttpOnly attribute to prevent access to the cookie via JavaScript. + */ + @ConfigItem(defaultValue = "false") + public boolean httpOnlyCookie; + + /** + * SameSite attribute for the session and location cookies. + */ + @ConfigItem(defaultValue = "strict") + public CookieSameSite cookieSameSite = CookieSameSite.STRICT; +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/BasicAuthenticationMechanism.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/BasicAuthenticationMechanism.java index 62bc6b8ae8ef5..7c16ecd8d00c6 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/BasicAuthenticationMechanism.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/BasicAuthenticationMechanism.java @@ -29,6 +29,7 @@ import java.util.Set; import java.util.regex.Pattern; +import jakarta.inject.Inject; import jakarta.inject.Singleton; import org.jboss.logging.Logger; @@ -41,6 +42,8 @@ import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.identity.request.AuthenticationRequest; import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest; +import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; +import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.smallrye.mutiny.Uni; import io.vertx.ext.web.RoutingContext; @@ -53,19 +56,6 @@ public class BasicAuthenticationMechanism implements HttpAuthenticationMechanism private static final Logger log = Logger.getLogger(BasicAuthenticationMechanism.class); - public static final String SILENT = "silent"; - public static final String CHARSET = "charset"; - /** - * A comma separated list of patterns and charsets. The pattern is a regular expression. - * - * Because different browsers use different encodings this allows for the correct encoding to be selected based - * on the current browser. In general though it is recommended that BASIC auth not be used when passwords contain - * characters outside ASCII, as some browsers use the current locate to determine encoding. - * - * This list must have an even number of elements, as it is interpreted as pattern,charset,pattern,charset,... - */ - public static final String USER_AGENT_CHARSETS = "user-agent-charsets"; - private final String challenge; private static final String BASIC = "basic"; @@ -85,6 +75,11 @@ public class BasicAuthenticationMechanism implements HttpAuthenticationMechanism private final Charset charset; private final Map userAgentCharsets; + @Inject + BasicAuthenticationMechanism(HttpConfiguration runtimeConfig, HttpBuildTimeConfig buildTimeConfig) { + this(runtimeConfig.auth.realm.orElse(null), buildTimeConfig.auth.form.enabled); + } + public BasicAuthenticationMechanism(final String realmName) { this(realmName, false); } @@ -101,25 +96,6 @@ public BasicAuthenticationMechanism(final String realmName, final boolean silent this.userAgentCharsets = Collections.unmodifiableMap(new LinkedHashMap<>(userAgentCharsets)); } - @Deprecated - public BasicAuthenticationMechanism(final String realmName, final String mechanismName) { - this(realmName, mechanismName, false); - } - - @Deprecated - public BasicAuthenticationMechanism(final String realmName, final String mechanismName, final boolean silent) { - this(realmName, mechanismName, silent, StandardCharsets.UTF_8, Collections.emptyMap()); - } - - @Deprecated - public BasicAuthenticationMechanism(final String realmName, final String mechanismName, final boolean silent, - Charset charset, Map userAgentCharsets) { - this.challenge = BASIC_PREFIX + "realm=\"" + realmName + "\""; - this.silent = silent; - this.charset = charset; - this.userAgentCharsets = Collections.unmodifiableMap(new LinkedHashMap<>(userAgentCharsets)); - } - @Override public Uni authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) { diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java index 48bfff2708aad..639565c632ad2 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java @@ -23,6 +23,7 @@ import io.quarkus.security.identity.request.TrustedAuthenticationRequest; import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest; import io.quarkus.vertx.http.runtime.FormAuthConfig; +import io.quarkus.vertx.http.runtime.FormAuthRuntimeConfig; import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.smallrye.mutiny.Uni; @@ -74,22 +75,23 @@ public class FormAuthenticationMechanism implements HttpAuthenticationMechanism key = httpConfiguration.encryptionKey.get(); } FormAuthConfig form = buildTimeConfig.auth.form; - this.loginManager = new PersistentLoginManager(key, form.cookieName, form.timeout.toMillis(), - form.newCookieInterval.toMillis(), form.httpOnlyCookie, form.cookieSameSite.name(), - form.cookiePath.orElse(null)); - this.loginPage = startWithSlash(form.loginPage.orElse(null)); - this.errorPage = startWithSlash(form.errorPage.orElse(null)); - this.landingPage = startWithSlash(form.landingPage.orElse(null)); + FormAuthRuntimeConfig runtimeForm = httpConfiguration.auth.form; + this.loginManager = new PersistentLoginManager(key, runtimeForm.cookieName, runtimeForm.timeout.toMillis(), + runtimeForm.newCookieInterval.toMillis(), runtimeForm.httpOnlyCookie, runtimeForm.cookieSameSite.name(), + runtimeForm.cookiePath.orElse(null)); + this.loginPage = startWithSlash(runtimeForm.loginPage.orElse(null)); + this.errorPage = startWithSlash(runtimeForm.errorPage.orElse(null)); + this.landingPage = startWithSlash(runtimeForm.landingPage.orElse(null)); this.postLocation = startWithSlash(form.postLocation); - this.usernameParameter = form.usernameParameter; - this.passwordParameter = form.passwordParameter; - this.locationCookie = form.locationCookie; - this.cookiePath = form.cookiePath.orElse(null); - boolean redirectAfterLogin = form.redirectAfterLogin; + this.usernameParameter = runtimeForm.usernameParameter; + this.passwordParameter = runtimeForm.passwordParameter; + this.locationCookie = runtimeForm.locationCookie; + this.cookiePath = runtimeForm.cookiePath.orElse(null); + boolean redirectAfterLogin = runtimeForm.redirectAfterLogin; this.redirectToLandingPage = landingPage != null && redirectAfterLogin; this.redirectToLoginPage = loginPage != null; this.redirectToErrorPage = errorPage != null; - this.cookieSameSite = CookieSameSite.valueOf(form.cookieSameSite.name()); + this.cookieSameSite = CookieSameSite.valueOf(runtimeForm.cookieSameSite.name()); } public FormAuthenticationMechanism(String loginPage, String postLocation, 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 40cc75234ddce..792238ec3f3d6 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 @@ -21,7 +21,6 @@ import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.identity.request.AnonymousAuthenticationRequest; import io.quarkus.security.spi.runtime.MethodDescription; -import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.smallrye.mutiny.CompositeException; import io.smallrye.mutiny.Uni; @@ -55,16 +54,6 @@ public void handle(RoutingContext event) { }; } - public Supplier setupBasicAuth(HttpBuildTimeConfig buildTimeConfig) { - return new Supplier() { - @Override - public BasicAuthenticationMechanism get() { - return new BasicAuthenticationMechanism(buildTimeConfig.auth.realm.orElse(null), - buildTimeConfig.auth.form.enabled); - } - }; - } - /** * This handler resolves the identity, and will be mapped to the post location. Otherwise, * for lazy auth the post will not be evaluated if there is no security rule for the post location.