Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem;
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.DescriptorUtils;
Expand Down Expand Up @@ -292,9 +293,10 @@ void createHttpAuthenticationHandler(HttpSecurityRecorder recorder, Capabilities
@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep
void initializeHttpSecurity(Optional<HttpAuthenticationHandlerBuildItem> authenticationHandler,
HttpSecurityRecorder recorder, BeanContainerBuildItem beanContainerBuildItem) {
HttpSecurityRecorder recorder, BeanContainerBuildItem beanContainerBuildItem,
ShutdownContextBuildItem shutdown) {
if (authenticationHandler.isPresent()) {
recorder.prepareHttpSecurityConfiguration();
recorder.prepareHttpSecurityConfiguration(shutdown);
recorder.initializeHttpAuthenticatorHandler(authenticationHandler.get().handler,
beanContainerBuildItem.getValue());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

Expand All @@ -23,21 +24,40 @@
import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest;
import io.quarkus.vertx.http.runtime.AuthRuntimeConfig;
import io.quarkus.vertx.http.runtime.PolicyMappingConfig;
import io.quarkus.vertx.http.runtime.VertxHttpBuildTimeConfig;
import io.quarkus.vertx.http.runtime.VertxHttpConfig;
import io.quarkus.vertx.http.runtime.security.annotation.BasicAuthentication;
import io.quarkus.vertx.http.security.HttpSecurity;
import io.smallrye.config.SmallRyeConfig;
import io.vertx.core.http.ClientAuth;

/**
* This singleton carries final HTTP Security configuration and act as a single source of truth for it.
* This class is not part of the public API and is subject to change.
*/
record HttpSecurityConfiguration(RolesMapping rolesMapping, List<HttpPermissionCarrier> httpPermissions,
Optional<Boolean> basicAuthEnabled, boolean formAuthEnabled, String formPostLocation,
List<HttpAuthenticationMechanism> additionalMechanisms) {
public final class HttpSecurityConfiguration {

private static final Logger LOG = Logger.getLogger(HttpSecurityConfiguration.class);

private static volatile HttpSecurityConfiguration instance = null;
private final RolesMapping rolesMapping;
private final List<HttpPermissionCarrier> httpPermissions;
private final Optional<Boolean> basicAuthEnabled;
private final boolean formAuthEnabled;
private final String formPostLocation;
private final List<HttpAuthenticationMechanism> additionalMechanisms;
private final VertxHttpConfig httpConfig;

private HttpSecurityConfiguration(RolesMapping rolesMapping, List<HttpPermissionCarrier> httpPermissions,
Optional<Boolean> basicAuthEnabled, boolean formAuthEnabled, String formPostLocation,
List<HttpAuthenticationMechanism> additionalMechanisms, VertxHttpConfig httpConfig) {
this.rolesMapping = rolesMapping;
this.httpPermissions = httpPermissions;
this.basicAuthEnabled = basicAuthEnabled;
this.formAuthEnabled = formAuthEnabled;
this.formPostLocation = formPostLocation;
this.additionalMechanisms = additionalMechanisms;
this.httpConfig = httpConfig;
}

record Policy(String name, HttpSecurityPolicy instance) {
}
Expand All @@ -64,7 +84,7 @@ default PolicyMappingConfig.AppliesTo getAppliesTo() {
}
}

BasicAuthenticationMechanism getBasicAuthenticationMechanism(VertxHttpConfig httpConfig) {
BasicAuthenticationMechanism getBasicAuthenticationMechanism() {
for (HttpAuthenticationMechanism additionalMechanism : additionalMechanisms) {
if (additionalMechanism.getClass() == BasicAuthenticationMechanism.class) {
return (BasicAuthenticationMechanism) additionalMechanism;
Expand All @@ -73,7 +93,7 @@ BasicAuthenticationMechanism getBasicAuthenticationMechanism(VertxHttpConfig htt
return new BasicAuthenticationMechanism(httpConfig.auth().realm().orElse(null), formAuthEnabled);
}

FormAuthenticationMechanism getFormAuthenticationMechanism(VertxHttpConfig httpConfig) {
FormAuthenticationMechanism getFormAuthenticationMechanism() {
for (HttpAuthenticationMechanism additionalMechanism : additionalMechanisms) {
if (additionalMechanism.getClass() == FormAuthenticationMechanism.class) {
return (FormAuthenticationMechanism) additionalMechanism;
Expand Down Expand Up @@ -123,52 +143,74 @@ public int compare(HttpAuthenticationMechanism mech1, HttpAuthenticationMechanis
return result;
}

static HttpSecurityConfiguration get() {
return get(null, null);
}

// this instance is not in the CDI container to avoid "potential" (I am guessing) circular dependencies
// during the bean instantiation as we can't be sure what users will inject when they observe the HTTP Security
static HttpSecurityConfiguration get() {
static HttpSecurityConfiguration get(VertxHttpConfig httpConfig, VertxHttpBuildTimeConfig httpBuildTimeConfig) {
var configInstance = instance;
if (configInstance == null) {
return initializeHttpSecurityConfiguration(httpConfig, httpBuildTimeConfig);
}
return configInstance;
}

static void clear() {
instance = null;
}

private static synchronized HttpSecurityConfiguration initializeHttpSecurityConfiguration(VertxHttpConfig httpConfig,
VertxHttpBuildTimeConfig httpBuildTimeConfig) {
if (instance == null) {
synchronized (HttpSecurityConfiguration.class) {
if (instance == null) {
SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
VertxHttpConfig vertxHttpConfig = config.getConfigMapping(VertxHttpConfig.class);
HttpSecurityImpl httpSecurity = prepareHttpSecurity(vertxHttpConfig.auth());
List<HttpAuthenticationMechanism> mechanisms = httpSecurity.getMechanisms();

Optional<Boolean> basicAuthEnabled = config.getOptionalValue("quarkus.http.auth.basic", boolean.class);
if (basicAuthEnabled.isEmpty() || !basicAuthEnabled.get()) {
for (HttpAuthenticationMechanism mechanism : mechanisms) {
// not using instance of as we are not considering subclasses
if (mechanism.getClass() == BasicAuthenticationMechanism.class) {
basicAuthEnabled = Optional.of(Boolean.TRUE);
break;
}
}
final VertxHttpConfig vertxHttpConfig;
final VertxHttpBuildTimeConfig vertxHttpBuildTimeConfig;
if (httpConfig == null) {
SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
vertxHttpConfig = config.getConfigMapping(VertxHttpConfig.class);
vertxHttpBuildTimeConfig = config.getConfigMapping(VertxHttpBuildTimeConfig.class);
} else {
vertxHttpConfig = httpConfig;
vertxHttpBuildTimeConfig = Objects.requireNonNull(httpBuildTimeConfig);
}
HttpSecurityImpl httpSecurity = prepareHttpSecurity(vertxHttpConfig,
vertxHttpBuildTimeConfig.tlsClientAuth());
List<HttpAuthenticationMechanism> mechanisms = httpSecurity.getMechanisms();

Optional<Boolean> basicAuthEnabled = vertxHttpBuildTimeConfig.auth().basic();
if (basicAuthEnabled.isEmpty() || !basicAuthEnabled.get()) {
for (HttpAuthenticationMechanism mechanism : mechanisms) {
// not using instance of as we are not considering subclasses
if (mechanism.getClass() == BasicAuthenticationMechanism.class) {
basicAuthEnabled = Optional.of(Boolean.TRUE);
break;
}
}
}

boolean formAuthEnabled = config.getValue("quarkus.http.auth.form.enabled", Boolean.class);
String formPostLocation = vertxHttpConfig.auth().form().postLocation();
if (!formAuthEnabled) {
for (HttpAuthenticationMechanism mechanism : mechanisms) {
// not using instance of as we are not considering subclasses
if (mechanism.getClass() == FormAuthenticationMechanism.class) {
formAuthEnabled = true;
formPostLocation = ((FormAuthenticationMechanism) mechanism).getPostLocation();
break;
}
}
boolean formAuthEnabled = vertxHttpBuildTimeConfig.auth().form();
String formPostLocation = vertxHttpConfig.auth().form().postLocation();
if (!formAuthEnabled) {
for (HttpAuthenticationMechanism mechanism : mechanisms) {
// not using instance of as we are not considering subclasses
if (mechanism.getClass() == FormAuthenticationMechanism.class) {
formAuthEnabled = true;
formPostLocation = ((FormAuthenticationMechanism) mechanism).getPostLocation();
break;
}

instance = new HttpSecurityConfiguration(httpSecurity.getRolesMapping(), httpSecurity.getHttpPermissions(),
basicAuthEnabled, formAuthEnabled, formPostLocation, mechanisms);
}
}

instance = new HttpSecurityConfiguration(httpSecurity.getRolesMapping(), httpSecurity.getHttpPermissions(),
basicAuthEnabled, formAuthEnabled, formPostLocation, mechanisms, httpConfig);
}
return instance;
}

private static HttpSecurityImpl prepareHttpSecurity(AuthRuntimeConfig authConfig) {
HttpSecurityImpl httpSecurity = new HttpSecurityImpl();
addAuthRuntimeConfigToHttpSecurity(authConfig, httpSecurity);
private static HttpSecurityImpl prepareHttpSecurity(VertxHttpConfig httpConfig, ClientAuth clientAuth) {
HttpSecurityImpl httpSecurity = new HttpSecurityImpl(clientAuth, httpConfig);
addAuthRuntimeConfigToHttpSecurity(httpConfig.auth(), httpSecurity);
Event<HttpSecurity> httpSecurityEvent = Arc.container().beanManager().getEvent().select(HttpSecurity.class);
httpSecurityEvent.fire(httpSecurity);
return httpSecurity;
Expand Down Expand Up @@ -317,12 +359,10 @@ private void addBasicAuthMechanismIfImplicitlyRequired(
}
}

private static boolean isBasicAuthNotRequired() {
private boolean isBasicAuthNotRequired() {
if (Boolean.getBoolean(BASIC_AUTH_ANNOTATION_DETECTED)) {
return false;
}
List<HttpSecurityConfiguration.HttpPermissionCarrier> httpPermissions = HttpSecurityConfiguration
.get().httpPermissions();
for (var permission : httpPermissions) {
if (permission.getAuthMechanism() != null
&& BasicAuthentication.AUTH_MECHANISM_SCHEME.equals(permission.getAuthMechanism().name())) {
Expand All @@ -331,4 +371,21 @@ private static boolean isBasicAuthNotRequired() {
}
return true;
}

RolesMapping rolesMapping() {
return rolesMapping;
}

List<HttpPermissionCarrier> httpPermissions() {
return httpPermissions;
}

boolean formAuthEnabled() {
return formAuthEnabled;
}

String formPostLocation() {
return formPostLocation;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
import java.util.function.BiPredicate;
import java.util.function.Predicate;

import org.eclipse.microprofile.config.ConfigProvider;
import org.jboss.logging.Logger;

import io.quarkus.security.StringPermission;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.vertx.http.runtime.FormAuthConfig;
import io.quarkus.vertx.http.runtime.VertxHttpBuildTimeConfig;
import io.quarkus.vertx.http.runtime.VertxHttpConfig;
import io.quarkus.vertx.http.runtime.security.HttpSecurityConfiguration.HttpPermissionCarrier;
import io.quarkus.vertx.http.runtime.security.HttpSecurityConfiguration.Policy;
Expand All @@ -27,7 +25,6 @@
import io.quarkus.vertx.http.runtime.security.annotation.MTLSAuthentication;
import io.quarkus.vertx.http.security.Basic;
import io.quarkus.vertx.http.security.HttpSecurity;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigBuilder;
import io.smallrye.mutiny.Uni;
import io.vertx.core.http.ClientAuth;
Expand All @@ -39,12 +36,16 @@ final class HttpSecurityImpl implements HttpSecurity {

private final List<HttpPermissionCarrier> httpPermissions;
private final List<HttpAuthenticationMechanism> mechanisms;
private final VertxHttpConfig vertxHttpConfig;
private RolesMapping rolesMapping;
private ClientAuth clientAuth;

HttpSecurityImpl() {
HttpSecurityImpl(ClientAuth clientAuth, VertxHttpConfig vertxHttpConfig) {
this.rolesMapping = null;
this.httpPermissions = new ArrayList<>();
this.mechanisms = new ArrayList<>();
this.clientAuth = clientAuth;
this.vertxHttpConfig = vertxHttpConfig;
}

@Override
Expand All @@ -58,15 +59,13 @@ public HttpSecurity mechanism(HttpAuthenticationMechanism mechanism) {
.build()
.getConfigMapping(VertxHttpConfig.class)
.auth().form();
final FormAuthConfig actualConfig = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class)
.getConfigMapping(VertxHttpConfig.class).auth().form();
final FormAuthConfig actualConfig = vertxHttpConfig.auth().form();
if (!actualConfig.equals(defaults)) {
throw new IllegalArgumentException("Cannot configure form-based authentication programmatically "
+ "because it has already been configured in the 'application.properties' file");
}
} else if (mechanism.getClass() == BasicAuthenticationMechanism.class) {
String actualRealm = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class)
.getConfigMapping(VertxHttpConfig.class).auth().realm().orElse(null);
String actualRealm = vertxHttpConfig.auth().realm().orElse(null);
if (actualRealm != null) {
throw new IllegalArgumentException("Cannot configure basic authentication programmatically because "
+ "the authentication realm has already been configured in the 'application.properties' file");
Expand Down Expand Up @@ -303,7 +302,7 @@ public HttpPermission form() {

@Override
public HttpPermission mTLS() {
boolean mTlsDisabled = ClientAuth.NONE.equals(getHttpBuildTimeConfig().tlsClientAuth());
boolean mTlsDisabled = ClientAuth.NONE.equals(clientAuth);
if (mTlsDisabled) {
throw new IllegalStateException(
"TLS client authentication is not available, please set the 'quarkus.http.ssl.client-auth'"
Expand Down Expand Up @@ -515,8 +514,7 @@ List<HttpAuthenticationMechanism> getMechanisms() {
return mechanisms.isEmpty() ? List.of() : List.copyOf(mechanisms);
}

private static VertxHttpBuildTimeConfig getHttpBuildTimeConfig() {
return ConfigProvider.getConfig().unwrap(SmallRyeConfig.class)
.getConfigMapping(VertxHttpBuildTimeConfig.class);
ClientAuth getClientAuth() {
return clientAuth;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import io.quarkus.arc.runtime.BeanContainer;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.ShutdownContext;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.security.AuthenticationCompletionException;
Expand All @@ -43,6 +44,7 @@
import io.quarkus.security.identity.request.AnonymousAuthenticationRequest;
import io.quarkus.security.spi.runtime.MethodDescription;
import io.quarkus.vertx.http.runtime.CurrentVertxRequest;
import io.quarkus.vertx.http.runtime.VertxHttpBuildTimeConfig;
import io.quarkus.vertx.http.runtime.VertxHttpConfig;
import io.smallrye.common.vertx.VertxContext;
import io.smallrye.mutiny.CompositeException;
Expand All @@ -63,9 +65,11 @@ public class HttpSecurityRecorder {
private static final Logger log = Logger.getLogger(HttpSecurityRecorder.class);

private final RuntimeValue<VertxHttpConfig> httpConfig;
private final VertxHttpBuildTimeConfig httpBuildTimeConfig;

public HttpSecurityRecorder(final RuntimeValue<VertxHttpConfig> httpConfig) {
public HttpSecurityRecorder(final RuntimeValue<VertxHttpConfig> httpConfig, VertxHttpBuildTimeConfig httpBuildTimeConfig) {
this.httpConfig = httpConfig;
this.httpBuildTimeConfig = httpBuildTimeConfig;
}

public RuntimeValue<AuthenticationHandler> authenticationMechanismHandler(boolean proactiveAuthentication,
Expand Down Expand Up @@ -180,16 +184,17 @@ public Map<String, Object> get() {
};
}

public void prepareHttpSecurityConfiguration() {
public void prepareHttpSecurityConfiguration(ShutdownContext shutdownContext) {
// this is done so that we prepare and validate HTTP Security config before the first incoming request
HttpSecurityConfiguration.get();
HttpSecurityConfiguration.get(httpConfig.getValue(), httpBuildTimeConfig);
shutdownContext.addShutdownTask(HttpSecurityConfiguration::clear);
}

public Supplier<FormAuthenticationMechanism> createFormAuthMechanism() {
return new Supplier<FormAuthenticationMechanism>() {
@Override
public FormAuthenticationMechanism get() {
return HttpSecurityConfiguration.get().getFormAuthenticationMechanism(httpConfig.getValue());
return HttpSecurityConfiguration.get().getFormAuthenticationMechanism();
}
};
}
Expand Down Expand Up @@ -580,7 +585,7 @@ public Supplier<BasicAuthenticationMechanism> basicAuthenticationMechanismBean()
return new Supplier<>() {
@Override
public BasicAuthenticationMechanism get() {
return HttpSecurityConfiguration.get().getBasicAuthenticationMechanism(httpConfig.getValue());
return HttpSecurityConfiguration.get().getBasicAuthenticationMechanism();
}
};
}
Expand Down
Loading