
io.quarkus.vertx.http.deployment.HttpSecurityProcessor Maven / Gradle / Ivy
package io.quarkus.vertx.http.deployment;
import java.security.Permission;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import jakarta.inject.Singleton;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerListenerBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.security.StringPermission;
import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig;
import io.quarkus.vertx.http.runtime.PolicyConfig;
import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig;
import io.quarkus.vertx.http.runtime.security.AuthenticatedHttpSecurityPolicy;
import io.quarkus.vertx.http.runtime.security.BasicAuthenticationMechanism;
import io.quarkus.vertx.http.runtime.security.DenySecurityPolicy;
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.HttpSecurityPolicy;
import io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder;
import io.quarkus.vertx.http.runtime.security.MtlsAuthenticationMechanism;
import io.quarkus.vertx.http.runtime.security.PathMatchingHttpSecurityPolicy;
import io.quarkus.vertx.http.runtime.security.PermitSecurityPolicy;
import io.quarkus.vertx.http.runtime.security.RolesAllowedHttpSecurityPolicy;
import io.quarkus.vertx.http.runtime.security.SupplierImpl;
import io.vertx.core.http.ClientAuth;
public class HttpSecurityProcessor {
private static final DotName PERMISSION = DotName.createSimple(Permission.class.getName());
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
public void builtins(BuildProducer producer,
BuildProducer reflectiveClassProducer,
CombinedIndexBuildItem combinedIndexBuildItem,
HttpBuildTimeConfig buildTimeConfig, HttpSecurityRecorder recorder,
BuildProducer beanProducer) {
producer.produce(new HttpSecurityPolicyBuildItem("deny", new SupplierImpl<>(new DenySecurityPolicy())));
producer.produce(new HttpSecurityPolicyBuildItem("permit", new SupplierImpl<>(new PermitSecurityPolicy())));
producer.produce(
new HttpSecurityPolicyBuildItem("authenticated", new SupplierImpl<>(new AuthenticatedHttpSecurityPolicy())));
if (!buildTimeConfig.auth.permissions.isEmpty()) {
beanProducer.produce(AdditionalBeanBuildItem.unremovableOf(PathMatchingHttpSecurityPolicy.class));
}
Map> permClassToCreator = new HashMap<>();
for (Map.Entry e : buildTimeConfig.auth.rolePolicy.entrySet()) {
PolicyConfig policyConfig = e.getValue();
if (policyConfig.permissions.isEmpty()) {
producer.produce(new HttpSecurityPolicyBuildItem(e.getKey(),
new SupplierImpl<>(new RolesAllowedHttpSecurityPolicy(e.getValue().rolesAllowed))));
} else {
// create HTTP Security policy that checks allowed roles and grants SecurityIdentity permissions to
// requests that this policy allows to proceed
var permissionCreator = permClassToCreator.computeIfAbsent(policyConfig.permissionClass,
new Function>() {
@Override
public BiFunction apply(String s) {
if (StringPermission.class.getName().equals(s)) {
return recorder.stringPermissionCreator();
}
boolean constructorAcceptsActions = validateConstructor(combinedIndexBuildItem.getIndex(),
policyConfig.permissionClass);
return recorder.customPermissionCreator(s, constructorAcceptsActions);
}
});
var policy = recorder.createRolesAllowedPolicy(policyConfig.rolesAllowed, policyConfig.permissions,
permissionCreator);
producer.produce(new HttpSecurityPolicyBuildItem(e.getKey(), policy));
}
}
if (!permClassToCreator.isEmpty()) {
// we need to register Permission classes for reflection as strictly speaking
// they might not exactly match classes defined via `PermissionsAllowed#permission`
var permissionClassesArr = permClassToCreator.keySet().toArray(new String[0]);
reflectiveClassProducer
.produce(ReflectiveClassBuildItem.builder(permissionClassesArr).constructors().fields().methods().build());
}
}
private static boolean validateConstructor(IndexView index, String permissionClass) {
ClassInfo classInfo = index.getClassByName(permissionClass);
if (classInfo == null) {
throw new ConfigurationException(String.format("Permission class '%s' is missing", permissionClass));
}
// must have exactly one constructor
if (classInfo.constructors().size() != 1) {
throw new ConfigurationException(
String.format("Permission class '%s' must have exactly one constructor", permissionClass));
}
MethodInfo constructor = classInfo.constructors().get(0);
// first parameter must be permission name (String)
if (constructor.parametersCount() == 0 || !isString(constructor.parameterType(0))) {
throw new ConfigurationException(
String.format("Permission class '%s' constructor first parameter must be '%s' (permission name)",
permissionClass, String.class.getName()));
}
// second parameter (actions) is optional
if (constructor.parametersCount() == 1) {
// permission constructor accepts just name, no actions
return false;
}
if (constructor.parametersCount() == 2) {
if (!isStringArray(constructor.parameterType(1))) {
throw new ConfigurationException(
String.format("Permission class '%s' constructor second parameter must be '%s' array", permissionClass,
String.class.getName()));
}
return true;
}
throw new ConfigurationException(String.format(
"Permission class '%s' constructor must accept either one parameter (String permissionName), or two parameters (String permissionName, String[] actions)",
permissionClass));
}
private static boolean isStringArray(Type type) {
return type.kind() == Type.Kind.ARRAY && isString(type.asArrayType().component());
}
private static boolean isString(Type type) {
return type.kind() == Type.Kind.CLASS && type.name().toString().equals(String.class.getName());
}
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
SyntheticBeanBuildItem initFormAuth(
HttpSecurityRecorder recorder,
HttpBuildTimeConfig buildTimeConfig,
BuildProducer filterBuildItemBuildProducer) {
if (!buildTimeConfig.auth.proactive) {
filterBuildItemBuildProducer.produce(RouteBuildItem.builder().route(buildTimeConfig.auth.form.postLocation)
.handler(recorder.formAuthPostHandler()).build());
}
if (buildTimeConfig.auth.form.enabled) {
return SyntheticBeanBuildItem.configure(FormAuthenticationMechanism.class)
.types(HttpAuthenticationMechanism.class)
.setRuntimeInit()
.scope(Singleton.class)
.supplier(recorder.setupFormAuth()).done();
}
return null;
}
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
SyntheticBeanBuildItem initMtlsClientAuth(
HttpSecurityRecorder recorder,
HttpBuildTimeConfig buildTimeConfig) {
if (isMtlsClientAuthenticationEnabled(buildTimeConfig)) {
return SyntheticBeanBuildItem.configure(MtlsAuthenticationMechanism.class)
.types(HttpAuthenticationMechanism.class)
.setRuntimeInit()
.scope(Singleton.class)
.supplier(recorder.setupMtlsClientAuth()).done();
}
return null;
}
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
SyntheticBeanBuildItem initBasicAuth(
HttpSecurityRecorder recorder,
HttpBuildTimeConfig buildTimeConfig,
ManagementInterfaceBuildTimeConfig managementInterfaceBuildTimeConfig,
BuildProducer securityInformationProducer) {
if (!applicationBasicAuthRequired(buildTimeConfig, managementInterfaceBuildTimeConfig)) {
return null;
}
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
.configure(BasicAuthenticationMechanism.class)
.types(HttpAuthenticationMechanism.class)
.setRuntimeInit()
.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();
securityInformationProducer.produce(SecurityInformationBuildItem.BASIC());
}
return configurator.done();
}
public static boolean applicationBasicAuthRequired(HttpBuildTimeConfig buildTimeConfig,
ManagementInterfaceBuildTimeConfig managementInterfaceBuildTimeConfig) {
//basic auth explicitly disabled
if (buildTimeConfig.auth.basic.isPresent() && !buildTimeConfig.auth.basic.get()) {
return false;
}
if (!buildTimeConfig.auth.basic.orElse(false)) {
if ((buildTimeConfig.auth.form.enabled || isMtlsClientAuthenticationEnabled(buildTimeConfig))
|| managementInterfaceBuildTimeConfig.auth.basic.orElse(false)) {
//if form auth is enabled and we are not then we don't install
return false;
}
}
return true;
}
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void setupAuthenticationMechanisms(
HttpSecurityRecorder recorder,
BuildProducer filterBuildItemBuildProducer,
BuildProducer beanProducer,
Capabilities capabilities,
BuildProducer beanContainerListenerBuildItemBuildProducer,
HttpBuildTimeConfig buildTimeConfig,
List httpSecurityPolicyBuildItemList,
BuildProducer securityInformationProducer) {
Map> policyMap = new HashMap<>();
for (HttpSecurityPolicyBuildItem e : httpSecurityPolicyBuildItemList) {
if (policyMap.containsKey(e.getName())) {
throw new RuntimeException("Multiple HTTP security policies defined with name " + e.getName());
}
policyMap.put(e.getName(), e.policySupplier);
}
if (!buildTimeConfig.auth.form.enabled && buildTimeConfig.auth.basic.orElse(false)) {
securityInformationProducer.produce(SecurityInformationBuildItem.BASIC());
}
if (capabilities.isPresent(Capability.SECURITY)) {
beanProducer
.produce(AdditionalBeanBuildItem.builder().setUnremovable().addBeanClass(HttpAuthenticator.class)
.addBeanClass(HttpAuthorizer.class).build());
filterBuildItemBuildProducer
.produce(new FilterBuildItem(
recorder.authenticationMechanismHandler(buildTimeConfig.auth.proactive),
FilterBuildItem.AUTHENTICATION));
filterBuildItemBuildProducer
.produce(new FilterBuildItem(recorder.permissionCheckHandler(), FilterBuildItem.AUTHORIZATION));
if (!buildTimeConfig.auth.permissions.isEmpty()) {
beanContainerListenerBuildItemBuildProducer
.produce(new BeanContainerListenerBuildItem(recorder.initPermissions(buildTimeConfig, policyMap)));
}
} else {
if (!buildTimeConfig.auth.permissions.isEmpty()) {
throw new IllegalStateException("HTTP permissions have been set however security is not enabled");
}
}
}
private static boolean isMtlsClientAuthenticationEnabled(HttpBuildTimeConfig buildTimeConfig) {
return !ClientAuth.NONE.equals(buildTimeConfig.tlsClientAuth);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy