mirror of
https://github.com/jlengrand/quarkus.git
synced 2026-03-10 08:41:22 +00:00
Merge pull request #5483 from geoand/spring-security-preparation
Make build registration of security interceptors more flexible
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package io.quarkus.resteasy.deployment;
|
||||
|
||||
import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
|
||||
import static io.quarkus.resteasy.deployment.SecurityTransformerUtils.hasSecurityAnnotation;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -10,7 +11,6 @@ import java.util.stream.Collectors;
|
||||
import org.jboss.jandex.ClassInfo;
|
||||
import org.jboss.jandex.DotName;
|
||||
|
||||
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
|
||||
import io.quarkus.deployment.Capabilities;
|
||||
import io.quarkus.deployment.IsDevelopment;
|
||||
import io.quarkus.deployment.annotations.BuildProducer;
|
||||
@@ -41,24 +41,19 @@ public class ResteasyBuiltinsProcessor {
|
||||
void setUpDenyAllJaxRs(CombinedIndexBuildItem index,
|
||||
JaxRsSecurityConfig config,
|
||||
ResteasyDeploymentBuildItem resteasyDeployment,
|
||||
BuildProducer<AnnotationsTransformerBuildItem> transformers,
|
||||
BuildProducer<AdditionalSecuredClassesBuildIem> additionalSecuredClasses) {
|
||||
if (config.denyJaxRs) {
|
||||
Set<ClassInfo> classes = new HashSet<>();
|
||||
|
||||
DenyJaxRsTransformer transformer = new DenyJaxRsTransformer(resteasyDeployment.getDeployment());
|
||||
|
||||
List<String> resourceClasses = resteasyDeployment.getDeployment().getScannedResourceClasses();
|
||||
for (String className : resourceClasses) {
|
||||
ClassInfo classInfo = index.getIndex().getClassByName(DotName.createSimple(className));
|
||||
if (transformer.requiresSyntheticDenyAll(classInfo)) {
|
||||
if (!hasSecurityAnnotation(classInfo)) {
|
||||
classes.add(classInfo);
|
||||
}
|
||||
}
|
||||
|
||||
additionalSecuredClasses.produce(new AdditionalSecuredClassesBuildIem(classes));
|
||||
transformers.produce(new AnnotationsTransformerBuildItem(transformer));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.quarkus.security.deployment;
|
||||
|
||||
import static io.quarkus.security.deployment.SecurityTransformerUtils.DENY_ALL;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.jandex.AnnotationTarget;
|
||||
|
||||
import io.quarkus.arc.processor.AnnotationsTransformer;
|
||||
|
||||
public class AdditionalDenyingUnannotatedTransformer implements AnnotationsTransformer {
|
||||
|
||||
private final Set<String> classNames;
|
||||
|
||||
public AdditionalDenyingUnannotatedTransformer(Collection<String> classNames) {
|
||||
this.classNames = new HashSet<>(classNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appliesTo(AnnotationTarget.Kind kind) {
|
||||
return kind == org.jboss.jandex.AnnotationTarget.Kind.CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(TransformationContext context) {
|
||||
String className = context.getTarget().asClass().name().toString();
|
||||
if (classNames.contains(className)) {
|
||||
context.transform().add(DENY_ALL).done();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.quarkus.security.deployment;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jboss.jandex.MethodInfo;
|
||||
|
||||
import io.quarkus.builder.item.MultiBuildItem;
|
||||
import io.quarkus.gizmo.BytecodeCreator;
|
||||
import io.quarkus.gizmo.ResultHandle;
|
||||
|
||||
/**
|
||||
* Used as an integration point when extensions need to customize the security behavior of a bean
|
||||
*/
|
||||
public final class AdditionalSecurityCheckBuildItem extends MultiBuildItem {
|
||||
|
||||
private final MethodInfo methodInfo;
|
||||
private final Function<BytecodeCreator, ResultHandle> function;
|
||||
|
||||
public AdditionalSecurityCheckBuildItem(MethodInfo methodInfo, Function<BytecodeCreator, ResultHandle> function) {
|
||||
this.methodInfo = methodInfo;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
public MethodInfo getMethodInfo() {
|
||||
return methodInfo;
|
||||
}
|
||||
|
||||
public Function<BytecodeCreator, ResultHandle> getSecurityCheckResultHandleCreator() {
|
||||
return function;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.quarkus.security.deployment;
|
||||
|
||||
import javax.annotation.security.DenyAll;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
|
||||
import org.jboss.jandex.DotName;
|
||||
|
||||
import io.quarkus.security.Authenticated;
|
||||
|
||||
public final class DotNames {
|
||||
|
||||
public static final DotName ROLES_ALLOWED = DotName.createSimple(RolesAllowed.class.getName());
|
||||
public static final DotName AUTHENTICATED = DotName.createSimple(Authenticated.class.getName());
|
||||
public static final DotName DENY_ALL = DotName.createSimple(DenyAll.class.getName());
|
||||
public static final DotName PERMIT_ALL = DotName.createSimple(PermitAll.class.getName());
|
||||
|
||||
private DotNames() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.quarkus.security.deployment;
|
||||
|
||||
import static io.quarkus.gizmo.MethodDescriptor.ofConstructor;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.quarkus.gizmo.BytecodeCreator;
|
||||
import io.quarkus.gizmo.ResultHandle;
|
||||
import io.quarkus.security.runtime.interceptor.check.AuthenticatedCheck;
|
||||
import io.quarkus.security.runtime.interceptor.check.DenyAllCheck;
|
||||
import io.quarkus.security.runtime.interceptor.check.PermitAllCheck;
|
||||
import io.quarkus.security.runtime.interceptor.check.RolesAllowedCheck;
|
||||
|
||||
public class SecurityCheckInstantiationUtil {
|
||||
|
||||
private SecurityCheckInstantiationUtil() {
|
||||
}
|
||||
|
||||
public static Function<BytecodeCreator, ResultHandle> rolesAllowedSecurityCheck(final String[] rolesAllowed) {
|
||||
return new Function<BytecodeCreator, ResultHandle>() {
|
||||
@Override
|
||||
public ResultHandle apply(BytecodeCreator creator) {
|
||||
if ((rolesAllowed == null)) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot use a null array to create an instance of " + RolesAllowedCheck.class.getName());
|
||||
}
|
||||
|
||||
ResultHandle ctorArgs = creator.newArray(String.class, creator.load(rolesAllowed.length));
|
||||
int i = 0;
|
||||
for (String val : rolesAllowed) {
|
||||
creator.writeArrayValue(ctorArgs, i++, creator.load(val));
|
||||
}
|
||||
return creator.newInstance(ofConstructor(RolesAllowedCheck.class, String[].class), ctorArgs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Function<BytecodeCreator, ResultHandle> denyAllSecurityCheck() {
|
||||
return new Function<BytecodeCreator, ResultHandle>() {
|
||||
@Override
|
||||
public ResultHandle apply(BytecodeCreator creator) {
|
||||
return creator.newInstance(ofConstructor(DenyAllCheck.class));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Function<BytecodeCreator, ResultHandle> permitAllSecurityCheck() {
|
||||
return new Function<BytecodeCreator, ResultHandle>() {
|
||||
@Override
|
||||
public ResultHandle apply(BytecodeCreator creator) {
|
||||
return creator.newInstance(ofConstructor(PermitAllCheck.class));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Function<BytecodeCreator, ResultHandle> authenticatedSecurityCheck() {
|
||||
return new Function<BytecodeCreator, ResultHandle>() {
|
||||
@Override
|
||||
public ResultHandle apply(BytecodeCreator creator) {
|
||||
return creator.newInstance(ofConstructor(AuthenticatedCheck.class));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
package io.quarkus.security.deployment;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import static io.quarkus.security.deployment.SecurityCheckInstantiationUtil.authenticatedSecurityCheck;
|
||||
import static io.quarkus.security.deployment.SecurityCheckInstantiationUtil.denyAllSecurityCheck;
|
||||
import static io.quarkus.security.deployment.SecurityCheckInstantiationUtil.permitAllSecurityCheck;
|
||||
import static io.quarkus.security.deployment.SecurityCheckInstantiationUtil.rolesAllowedSecurityCheck;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jboss.jandex.AnnotationInstance;
|
||||
import org.jboss.jandex.AnnotationTarget;
|
||||
import org.jboss.jandex.AnnotationValue;
|
||||
import org.jboss.jandex.ClassInfo;
|
||||
import org.jboss.jandex.DotName;
|
||||
import org.jboss.jandex.Index;
|
||||
import org.jboss.jandex.MethodInfo;
|
||||
import org.jboss.jandex.Type;
|
||||
import org.jboss.logging.Logger;
|
||||
@@ -26,10 +29,8 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
|
||||
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
|
||||
import io.quarkus.arc.deployment.BeanRegistrarBuildItem;
|
||||
import io.quarkus.arc.deployment.InterceptorBindingRegistrarBuildItem;
|
||||
import io.quarkus.arc.processor.AnnotationStore;
|
||||
import io.quarkus.arc.processor.BeanConfigurator;
|
||||
import io.quarkus.arc.processor.BeanRegistrar;
|
||||
import io.quarkus.arc.processor.BuildExtension;
|
||||
import io.quarkus.arc.processor.BuiltinScope;
|
||||
import io.quarkus.deployment.Capabilities;
|
||||
import io.quarkus.deployment.annotations.BuildProducer;
|
||||
@@ -39,6 +40,7 @@ import io.quarkus.deployment.builditem.ApplicationIndexBuildItem;
|
||||
import io.quarkus.deployment.builditem.CapabilityBuildItem;
|
||||
import io.quarkus.deployment.builditem.FeatureBuildItem;
|
||||
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
|
||||
import io.quarkus.gizmo.BytecodeCreator;
|
||||
import io.quarkus.gizmo.MethodCreator;
|
||||
import io.quarkus.gizmo.MethodDescriptor;
|
||||
import io.quarkus.gizmo.ResultHandle;
|
||||
@@ -54,6 +56,7 @@ import io.quarkus.security.runtime.interceptor.SecurityCheckStorage;
|
||||
import io.quarkus.security.runtime.interceptor.SecurityCheckStorageBuilder;
|
||||
import io.quarkus.security.runtime.interceptor.SecurityConstrainer;
|
||||
import io.quarkus.security.runtime.interceptor.SecurityHandler;
|
||||
import io.quarkus.security.runtime.interceptor.check.SecurityCheck;
|
||||
import io.quarkus.security.spi.AdditionalSecuredClassesBuildIem;
|
||||
|
||||
public class SecurityProcessor {
|
||||
@@ -94,14 +97,6 @@ public class SecurityProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
@BuildStep
|
||||
void transformSecurityAnnotations(BuildProducer<AnnotationsTransformerBuildItem> transformers,
|
||||
SecurityBuildTimeConfig config) {
|
||||
if (config.denyUnannotated) {
|
||||
transformers.produce(new AnnotationsTransformerBuildItem(new DenyingUnannotatedTransformer()));
|
||||
}
|
||||
}
|
||||
|
||||
@BuildStep
|
||||
void registerSecurityInterceptors(BuildProducer<InterceptorBindingRegistrarBuildItem> registrars,
|
||||
BuildProducer<AdditionalBeanBuildItem> beans) {
|
||||
@@ -112,44 +107,71 @@ public class SecurityProcessor {
|
||||
beans.produce(new AdditionalBeanBuildItem(SecurityHandler.class, SecurityConstrainer.class));
|
||||
}
|
||||
|
||||
/*
|
||||
* The annotation store is not meant to be generally supported for security annotation.
|
||||
* It is only used here in order to be able to register the DenyAllInterceptor for
|
||||
* methods that don't have a security annotation
|
||||
*/
|
||||
@BuildStep
|
||||
void transformSecurityAnnotations(BuildProducer<AnnotationsTransformerBuildItem> transformers,
|
||||
List<AdditionalSecuredClassesBuildIem> additionalSecuredClasses,
|
||||
SecurityBuildTimeConfig config) {
|
||||
if (config.denyUnannotated) {
|
||||
transformers.produce(new AnnotationsTransformerBuildItem(new DenyingUnannotatedTransformer()));
|
||||
}
|
||||
if (!additionalSecuredClasses.isEmpty()) {
|
||||
Set<String> additionalSecured = new HashSet<>();
|
||||
for (AdditionalSecuredClassesBuildIem securedClasses : additionalSecuredClasses) {
|
||||
for (ClassInfo additionalSecuredClass : securedClasses.additionalSecuredClasses) {
|
||||
additionalSecured.add(additionalSecuredClass.name().toString());
|
||||
}
|
||||
}
|
||||
transformers.produce(
|
||||
new AnnotationsTransformerBuildItem(new AdditionalDenyingUnannotatedTransformer(additionalSecured)));
|
||||
}
|
||||
}
|
||||
|
||||
@BuildStep
|
||||
void gatherSecurityChecks(BuildProducer<BeanRegistrarBuildItem> beanRegistrars,
|
||||
ApplicationIndexBuildItem indexBuildItem,
|
||||
BuildProducer<ApplicationClassPredicateBuildItem> classPredicate,
|
||||
List<AdditionalSecuredClassesBuildIem> additionalSecuredClasses) {
|
||||
List<AdditionalSecuredClassesBuildIem> additionalSecuredClasses,
|
||||
List<AdditionalSecurityCheckBuildItem> additionalSecurityChecks, SecurityBuildTimeConfig config) {
|
||||
classPredicate.produce(new ApplicationClassPredicateBuildItem(new SecurityCheckStorage.AppPredicate()));
|
||||
|
||||
Set<ClassInfo> additionalSecured = new HashSet<>();
|
||||
if (additionalSecuredClasses != null) {
|
||||
for (AdditionalSecuredClassesBuildIem securedClasses : additionalSecuredClasses) {
|
||||
additionalSecured.addAll(securedClasses.additionalSecuredClasses);
|
||||
}
|
||||
for (AdditionalSecuredClassesBuildIem securedClasses : additionalSecuredClasses) {
|
||||
additionalSecured.addAll(securedClasses.additionalSecuredClasses);
|
||||
}
|
||||
|
||||
beanRegistrars.produce(new BeanRegistrarBuildItem(new BeanRegistrar() {
|
||||
|
||||
@Override
|
||||
public void register(RegistrationContext registrationContext) {
|
||||
|
||||
Map<MethodInfo, AnnotationInstance> methodAnnotations = gatherSecurityAnnotationsByLooping(indexBuildItem,
|
||||
registrationContext, additionalSecured);
|
||||
Map<MethodInfo, Function<BytecodeCreator, ResultHandle>> securityChecks = gatherSecurityAnnotations(
|
||||
indexBuildItem, additionalSecured, config.denyUnannotated);
|
||||
for (AdditionalSecurityCheckBuildItem additionalSecurityCheck : additionalSecurityChecks) {
|
||||
securityChecks.put(additionalSecurityCheck.getMethodInfo(),
|
||||
additionalSecurityCheck.getSecurityCheckResultHandleCreator());
|
||||
}
|
||||
|
||||
DotName name = DotName.createSimple(SecurityCheckStorage.class.getName());
|
||||
|
||||
BeanConfigurator<Object> configurator = registrationContext.configure(name);
|
||||
configurator.addType(name);
|
||||
configurator.scope(BuiltinScope.APPLICATION.getInfo());
|
||||
configurator.creator(m -> {
|
||||
ResultHandle storageBuilder = m
|
||||
configurator.creator(creator -> {
|
||||
ResultHandle storageBuilder = creator
|
||||
.newInstance(MethodDescriptor.ofConstructor(SecurityCheckStorageBuilder.class));
|
||||
for (Map.Entry<MethodInfo, AnnotationInstance> methodEntry : methodAnnotations.entrySet()) {
|
||||
registerSecuredMethod(storageBuilder, m, methodEntry);
|
||||
for (Map.Entry<MethodInfo, Function<BytecodeCreator, ResultHandle>> methodEntry : securityChecks
|
||||
.entrySet()) {
|
||||
registerSecuredMethod(storageBuilder, creator, methodEntry);
|
||||
}
|
||||
ResultHandle ret = m.invokeVirtualMethod(
|
||||
ResultHandle ret = creator.invokeVirtualMethod(
|
||||
MethodDescriptor.ofMethod(SecurityCheckStorageBuilder.class, "create",
|
||||
SecurityCheckStorage.class),
|
||||
storageBuilder);
|
||||
m.returnValue(ret);
|
||||
creator.returnValue(ret);
|
||||
});
|
||||
configurator.done();
|
||||
}
|
||||
@@ -158,39 +180,17 @@ public class SecurityProcessor {
|
||||
|
||||
private void registerSecuredMethod(ResultHandle checkStorage,
|
||||
MethodCreator methodCreator,
|
||||
Map.Entry<MethodInfo, AnnotationInstance> methodEntry) {
|
||||
try {
|
||||
MethodInfo method = methodEntry.getKey();
|
||||
ResultHandle aClass = methodCreator.load(method.declaringClass().name().toString());
|
||||
ResultHandle methodName = methodCreator.load(method.name());
|
||||
ResultHandle params = paramTypes(methodCreator, method.parameters());
|
||||
Map.Entry<MethodInfo, Function<BytecodeCreator, ResultHandle>> methodEntry) {
|
||||
MethodInfo methodInfo = methodEntry.getKey();
|
||||
ResultHandle declaringClass = methodCreator.load(methodInfo.declaringClass().name().toString());
|
||||
ResultHandle methodName = methodCreator.load(methodInfo.name());
|
||||
ResultHandle methodParamTypes = paramTypes(methodCreator, methodInfo.parameters());
|
||||
|
||||
AnnotationInstance instance = methodEntry.getValue();
|
||||
ResultHandle securityAnnotation = methodCreator.load(instance.name().toString());
|
||||
|
||||
ResultHandle annotationParameters = annotationValues(methodCreator, instance);
|
||||
|
||||
Method registerAnnotation = SecurityCheckStorageBuilder.class.getDeclaredMethod("registerAnnotation",
|
||||
String.class, String.class, String[].class, String.class, String[].class);
|
||||
methodCreator.invokeVirtualMethod(MethodDescriptor.ofMethod(registerAnnotation), checkStorage,
|
||||
aClass, methodName, params, securityAnnotation, annotationParameters);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalStateException("registerAnnotation method not found on on SecurityCheckStorage", e);
|
||||
}
|
||||
}
|
||||
|
||||
private ResultHandle annotationValues(MethodCreator methodCreator, AnnotationInstance instance) {
|
||||
AnnotationValue value = instance.value();
|
||||
if (value != null && value.asStringArray() != null) {
|
||||
String[] values = value.asStringArray();
|
||||
ResultHandle result = methodCreator.newArray(String.class, methodCreator.load(values.length));
|
||||
int i = 0;
|
||||
for (String val : values) {
|
||||
methodCreator.writeArrayValue(result, i++, methodCreator.load(val));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return methodCreator.loadNull();
|
||||
methodCreator.invokeVirtualMethod(
|
||||
MethodDescriptor.ofMethod(SecurityCheckStorageBuilder.class, "registerCheck", void.class, String.class,
|
||||
String.class, String[].class, SecurityCheck.class),
|
||||
checkStorage,
|
||||
declaringClass, methodName, methodParamTypes, methodEntry.getValue().apply(methodCreator));
|
||||
}
|
||||
|
||||
private ResultHandle paramTypes(MethodCreator ctor, List<Type> parameters) {
|
||||
@@ -203,61 +203,106 @@ public class SecurityProcessor {
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<MethodInfo, AnnotationInstance> gatherSecurityAnnotationsByLooping(ApplicationIndexBuildItem indexBuildItem,
|
||||
BeanRegistrar.RegistrationContext registrationContext,
|
||||
Set<ClassInfo> additionalSecuredClasses) {
|
||||
Set<DotName> securityAnnotations = SecurityAnnotationsRegistrar.SECURITY_BINDINGS.keySet();
|
||||
AnnotationStore annotationStore = registrationContext.get(BuildExtension.Key.ANNOTATION_STORE);
|
||||
Set<ClassInfo> classesWithSecurity = new HashSet<>(additionalSecuredClasses);
|
||||
private Map<MethodInfo, Function<BytecodeCreator, ResultHandle>> gatherSecurityAnnotations(
|
||||
ApplicationIndexBuildItem indexBuildItem,
|
||||
Set<ClassInfo> additionalSecuredClasses, boolean denyUnannotated) {
|
||||
|
||||
Index index = indexBuildItem.getIndex();
|
||||
for (DotName securityAnno : SecurityAnnotationsRegistrar.SECURITY_BINDINGS.keySet()) {
|
||||
for (AnnotationInstance annotation : index.getAnnotations(securityAnno)) {
|
||||
AnnotationTarget target = annotation.target();
|
||||
switch (target.kind()) {
|
||||
case CLASS:
|
||||
classesWithSecurity.add(target.asClass());
|
||||
break;
|
||||
case METHOD:
|
||||
classesWithSecurity.add(target.asMethod().declaringClass());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Security annotation discovered on unsupported target: " + target);
|
||||
Map<MethodInfo, AnnotationInstance> methodToInstanceCollector = new HashMap<>();
|
||||
Map<MethodInfo, Function<BytecodeCreator, ResultHandle>> result = new HashMap<>(gatherSecurityAnnotations(
|
||||
indexBuildItem, DotNames.ROLES_ALLOWED, methodToInstanceCollector,
|
||||
(instance -> rolesAllowedSecurityCheck(instance.value().asStringArray()))));
|
||||
result.putAll(gatherSecurityAnnotations(indexBuildItem, DotNames.PERMIT_ALL, methodToInstanceCollector,
|
||||
(instance -> permitAllSecurityCheck())));
|
||||
result.putAll(gatherSecurityAnnotations(indexBuildItem, DotNames.AUTHENTICATED, methodToInstanceCollector,
|
||||
(instance -> authenticatedSecurityCheck())));
|
||||
|
||||
result.putAll(gatherSecurityAnnotations(indexBuildItem, DotNames.DENY_ALL, methodToInstanceCollector,
|
||||
(instance -> denyAllSecurityCheck())));
|
||||
|
||||
/*
|
||||
* Handle additional secured classes by adding the denyAll check to all public non-static methods
|
||||
* that don't have security annotations
|
||||
*/
|
||||
for (ClassInfo additionalSecureClassInfo : additionalSecuredClasses) {
|
||||
for (MethodInfo methodInfo : additionalSecureClassInfo.methods()) {
|
||||
if (!isPublicNonStaticNonConstructor(methodInfo)) {
|
||||
continue;
|
||||
}
|
||||
AnnotationInstance alreadyExistingInstance = methodToInstanceCollector.get(methodInfo);
|
||||
if ((alreadyExistingInstance == null)) {
|
||||
result.put(methodInfo, denyAllSecurityCheck());
|
||||
} else if (alreadyExistingInstance.target().kind() == AnnotationTarget.Kind.CLASS) {
|
||||
throw new IllegalStateException("Class " + methodInfo.declaringClass()
|
||||
+ " should not have been added as an additional secured class");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gatherSecurityAnnotations(securityAnnotations,
|
||||
classesWithSecurity, annotationStore);
|
||||
}
|
||||
|
||||
private Map<MethodInfo, AnnotationInstance> gatherSecurityAnnotations(Set<DotName> securityAnnotations,
|
||||
Set<ClassInfo> classesWithSecurity,
|
||||
AnnotationStore annotationStore) {
|
||||
Map<MethodInfo, AnnotationInstance> methodAnnotations = new HashMap<>();
|
||||
for (ClassInfo classInfo : classesWithSecurity) {
|
||||
Collection<AnnotationInstance> classAnnotations = annotationStore.getAnnotations(classInfo);
|
||||
AnnotationInstance classLevelAnnotation = getSingle(classAnnotations, securityAnnotations);
|
||||
|
||||
for (MethodInfo method : classInfo.methods()) {
|
||||
AnnotationInstance methodAnnotation = getSingle(annotationStore.getAnnotations(method), securityAnnotations);
|
||||
methodAnnotation = methodAnnotation == null ? classLevelAnnotation : methodAnnotation;
|
||||
if (methodAnnotation != null) {
|
||||
methodAnnotations.put(method, methodAnnotation);
|
||||
/*
|
||||
* If we need to add the denyAll security check to all unannotated methods, we simply go through all secured methods,
|
||||
* collect the declaring classes, then go through all methods of the classes and add the necessary check
|
||||
*/
|
||||
if (denyUnannotated) {
|
||||
Set<ClassInfo> allClassesWithSecurityChecks = new HashSet<>(methodToInstanceCollector.keySet().size());
|
||||
for (MethodInfo methodInfo : methodToInstanceCollector.keySet()) {
|
||||
allClassesWithSecurityChecks.add(methodInfo.declaringClass());
|
||||
}
|
||||
for (ClassInfo classWithSecurityCheck : allClassesWithSecurityChecks) {
|
||||
for (MethodInfo methodInfo : classWithSecurityCheck.methods()) {
|
||||
if (!isPublicNonStaticNonConstructor(methodInfo)) {
|
||||
continue;
|
||||
}
|
||||
if (methodToInstanceCollector.containsKey(methodInfo)) { // the method already has a security check
|
||||
continue;
|
||||
}
|
||||
result.put(methodInfo, denyAllSecurityCheck());
|
||||
}
|
||||
}
|
||||
}
|
||||
return methodAnnotations;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private AnnotationInstance getSingle(Collection<AnnotationInstance> classAnnotations, Set<DotName> securityAnnotations) {
|
||||
AnnotationInstance result = null;
|
||||
for (AnnotationInstance annotation : classAnnotations) {
|
||||
if (securityAnnotations.contains(annotation.name())) {
|
||||
if (result != null) {
|
||||
throw new IllegalStateException("Multiple security annotations on target: " + annotation.target());
|
||||
private boolean isPublicNonStaticNonConstructor(MethodInfo methodInfo) {
|
||||
return Modifier.isPublic(methodInfo.flags()) && !Modifier.isStatic(methodInfo.flags())
|
||||
&& !"<init>".equals(methodInfo.name());
|
||||
}
|
||||
|
||||
private Map<MethodInfo, Function<BytecodeCreator, ResultHandle>> gatherSecurityAnnotations(
|
||||
ApplicationIndexBuildItem indexBuildItem, DotName dotName,
|
||||
Map<MethodInfo, AnnotationInstance> alreadyCheckedMethods,
|
||||
Function<AnnotationInstance, Function<BytecodeCreator, ResultHandle>> securityCheckInstanceCreator) {
|
||||
|
||||
Map<MethodInfo, Function<BytecodeCreator, ResultHandle>> result = new HashMap<>();
|
||||
|
||||
List<AnnotationInstance> instances = indexBuildItem.getIndex().getAnnotations(dotName);
|
||||
// make sure we process annotations on methods first
|
||||
for (AnnotationInstance instance : instances) {
|
||||
AnnotationTarget target = instance.target();
|
||||
if (target.kind() == AnnotationTarget.Kind.METHOD) {
|
||||
MethodInfo methodInfo = target.asMethod();
|
||||
if (alreadyCheckedMethods.containsKey(methodInfo)) {
|
||||
throw new IllegalStateException("Method " + methodInfo.name() + " of class " + methodInfo.declaringClass()
|
||||
+ " is annotated with multiple security annotations");
|
||||
}
|
||||
alreadyCheckedMethods.put(methodInfo, instance);
|
||||
result.put(methodInfo, securityCheckInstanceCreator.apply(instance));
|
||||
}
|
||||
}
|
||||
// now add the class annotations to methods if they haven't already been annotated
|
||||
for (AnnotationInstance instance : instances) {
|
||||
AnnotationTarget target = instance.target();
|
||||
if (target.kind() == AnnotationTarget.Kind.CLASS) {
|
||||
List<MethodInfo> methods = target.asClass().methods();
|
||||
for (MethodInfo methodInfo : methods) {
|
||||
AnnotationInstance alreadyExistingInstance = alreadyCheckedMethods.get(methodInfo);
|
||||
if ((alreadyExistingInstance == null)) {
|
||||
result.put(methodInfo, securityCheckInstanceCreator.apply(instance));
|
||||
} else if (alreadyExistingInstance.target().kind() == AnnotationTarget.Kind.CLASS) {
|
||||
throw new IllegalStateException(
|
||||
"Class " + methodInfo.declaringClass() + " is annotated with multiple security annotations");
|
||||
}
|
||||
}
|
||||
result = annotation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,27 +6,16 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.security.DenyAll;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
|
||||
import io.quarkus.security.Authenticated;
|
||||
import io.quarkus.security.runtime.interceptor.check.AuthenticatedCheck;
|
||||
import io.quarkus.security.runtime.interceptor.check.DenyAllCheck;
|
||||
import io.quarkus.security.runtime.interceptor.check.PermitAllCheck;
|
||||
import io.quarkus.security.runtime.interceptor.check.RolesAllowedCheck;
|
||||
import io.quarkus.security.runtime.interceptor.check.SecurityCheck;
|
||||
|
||||
public class SecurityCheckStorageBuilder {
|
||||
private final Map<MethodDescription, SecurityCheck> securityChecks = new HashMap<>();
|
||||
|
||||
public void registerAnnotation(String aClass,
|
||||
public void registerCheck(String className,
|
||||
String methodName,
|
||||
String[] parameterTypes,
|
||||
String securityAnnotation,
|
||||
String[] value) {
|
||||
securityChecks.put(new MethodDescription(aClass, methodName, parameterTypes),
|
||||
determineCheck(securityAnnotation, value));
|
||||
SecurityCheck securityCheck) {
|
||||
securityChecks.put(new MethodDescription(className, methodName, parameterTypes), securityCheck);
|
||||
}
|
||||
|
||||
public SecurityCheckStorage create() {
|
||||
@@ -40,23 +29,7 @@ public class SecurityCheckStorageBuilder {
|
||||
};
|
||||
}
|
||||
|
||||
private SecurityCheck determineCheck(String securityAnnotation, String[] value) {
|
||||
if (DenyAll.class.getName().equals(securityAnnotation)) {
|
||||
return new DenyAllCheck();
|
||||
}
|
||||
if (RolesAllowed.class.getName().equals(securityAnnotation)) {
|
||||
return new RolesAllowedCheck(value);
|
||||
}
|
||||
if (PermitAll.class.getName().equals(securityAnnotation)) {
|
||||
return new PermitAllCheck();
|
||||
}
|
||||
if (Authenticated.class.getName().equals(securityAnnotation)) {
|
||||
return new AuthenticatedCheck();
|
||||
}
|
||||
throw new IllegalArgumentException("Unsupported security check " + securityAnnotation);
|
||||
}
|
||||
|
||||
private String[] typesAsStrings(Class[] parameterTypes) {
|
||||
private String[] typesAsStrings(Class<?>[] parameterTypes) {
|
||||
String[] result = new String[parameterTypes.length];
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
result[i] = parameterTypes[i].getName();
|
||||
@@ -69,8 +42,8 @@ public class SecurityCheckStorageBuilder {
|
||||
private final String methodName;
|
||||
private final String[] parameterTypes;
|
||||
|
||||
public MethodDescription(String aClass, String methodName, String[] parameterTypes) {
|
||||
this.className = aClass;
|
||||
public MethodDescription(String className, String methodName, String[] parameterTypes) {
|
||||
this.className = className;
|
||||
this.methodName = methodName;
|
||||
this.parameterTypes = parameterTypes;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import org.jboss.jandex.ClassInfo;
|
||||
|
||||
import io.quarkus.builder.item.MultiBuildItem;
|
||||
|
||||
/**
|
||||
* Contains classes that need to have @DenyAll on all methods that don't have security annotations
|
||||
*/
|
||||
public final class AdditionalSecuredClassesBuildIem extends MultiBuildItem {
|
||||
public final Collection<ClassInfo> additionalSecuredClasses;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user