Skip to content

Commit

Permalink
CDI security determined at build time, fixes quarkusio#4992
Browse files Browse the repository at this point in the history
  • Loading branch information
michalszynkiewicz authored and mmusgrov committed Dec 12, 2019
1 parent cfa1965 commit 0933b71
Show file tree
Hide file tree
Showing 11 changed files with 357 additions and 164 deletions.
2 changes: 2 additions & 0 deletions build-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
<scala.version>2.12.8</scala.version>
<scala-plugin.version>4.1.1</scala-plugin.version>

<version.surefire.plugin>2.22.1</version.surefire.plugin>

<!-- These properties are needed in order for them to be resolvable by the documentation -->
<!-- The Graal version we suggest using in documentation - as that's
what we work with by self downloading it: -->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
package io.quarkus.security.deployment;

import java.lang.reflect.Method;
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 org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;

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;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CapabilityBuildItem;
import io.quarkus.deployment.builditem.ApplicationIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.security.runtime.IdentityProviderManagerCreator;
import io.quarkus.security.runtime.SecurityBuildTimeConfig;
import io.quarkus.security.runtime.SecurityIdentityAssociation;
Expand All @@ -26,6 +47,8 @@
import io.quarkus.security.runtime.interceptor.DenyAllInterceptor;
import io.quarkus.security.runtime.interceptor.PermitAllInterceptor;
import io.quarkus.security.runtime.interceptor.RolesAllowedInterceptor;
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;

Expand Down Expand Up @@ -85,6 +108,145 @@ void registerSecurityInterceptors(BuildProducer<InterceptorBindingRegistrarBuild
beans.produce(new AdditionalBeanBuildItem(SecurityHandler.class, SecurityConstrainer.class));
}

@BuildStep
void gatherSecurityChecks(BuildProducer<BeanRegistrarBuildItem> beanRegistrars,
ApplicationIndexBuildItem indexBuildItem) {

beanRegistrars.produce(new BeanRegistrarBuildItem(new BeanRegistrar() {

@Override
public void register(RegistrationContext registrationContext) {
Map<MethodInfo, AnnotationInstance> methodAnnotations = gatherSecurityAnnotations(indexBuildItem,
registrationContext);

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
.newInstance(MethodDescriptor.ofConstructor(SecurityCheckStorageBuilder.class));
for (Map.Entry<MethodInfo, AnnotationInstance> methodEntry : methodAnnotations.entrySet()) {
registerSecuredMethod(storageBuilder, m, methodEntry);
}
ResultHandle ret = m.invokeVirtualMethod(
MethodDescriptor.ofMethod(SecurityCheckStorageBuilder.class, "create",
SecurityCheckStorage.class),
storageBuilder);
m.returnValue(ret);
});
configurator.done();
}
}));
}

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());

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();
}

private ResultHandle paramTypes(MethodCreator ctor, List<Type> parameters) {
ResultHandle result = ctor.newArray(String.class, ctor.load(parameters.size()));

for (int i = 0; i < parameters.size(); i++) {
ctor.writeArrayValue(result, i, ctor.load(parameters.get(i).toString()));
}

return result;
}

private Map<MethodInfo, AnnotationInstance> gatherSecurityAnnotations(ApplicationIndexBuildItem indexBuildItem,
BeanRegistrar.RegistrationContext registrationContext) {
Set<DotName> securityAnnotations = SecurityAnnotationsRegistrar.SECURITY_BINDINGS.keySet();
AnnotationStore annotationStore = registrationContext.get(BuildExtension.Key.ANNOTATION_STORE);
Set<ClassInfo> classesWithSecurity = new HashSet<>();

Collection<ClassInfo> classes = indexBuildItem.getIndex().getKnownClasses();
for (ClassInfo classInfo : classes) {
boolean hasSecurityAnnotations = annotationStore.hasAnyAnnotation(classInfo, securityAnnotations);
if (!hasSecurityAnnotations) {
for (MethodInfo method : classInfo.methods()) {
if (annotationStore.hasAnyAnnotation(method, securityAnnotations)) {
hasSecurityAnnotations = true;
break;
}
}
}
if (hasSecurityAnnotations) {
classesWithSecurity.add(classInfo);
}
}

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);
}
}
}
return methodAnnotations;
}

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("Duplicate security annotations on class " + annotation.target());
}
result = annotation;
}
}

return result;
}

/**
* Determine the classes that make up the provider and its services
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.quarkus.security.runtime.interceptor;

import java.lang.reflect.Method;

import io.quarkus.security.runtime.interceptor.check.SecurityCheck;

public interface SecurityCheckStorage {
SecurityCheck getSecurityCheck(Method method);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package io.quarkus.security.runtime.interceptor;

import java.lang.reflect.Method;
import java.util.Arrays;
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,
String methodName,
String[] parameterTypes,
String securityAnnotation,
String[] value) {
securityChecks.put(new MethodDescription(aClass, methodName, parameterTypes),
determineCheck(securityAnnotation, value));
}

public SecurityCheckStorage create() {
return new SecurityCheckStorage() {
@Override
public SecurityCheck getSecurityCheck(Method method) {
MethodDescription descriptor = new MethodDescription(method.getDeclaringClass().getName(), method.getName(),
typesAsStrings(method.getParameterTypes()));
return securityChecks.get(descriptor);
}
};
}

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) {
String[] result = new String[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
result[i] = parameterTypes[i].getName();
}
return result;
}

static class MethodDescription {
private final String className;
private final String methodName;
private final String[] parameterTypes;

public MethodDescription(String aClass, String methodName, String[] parameterTypes) {
this.className = aClass;
this.methodName = methodName;
this.parameterTypes = parameterTypes;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
MethodDescription that = (MethodDescription) o;
return className.equals(that.className) &&
methodName.equals(that.methodName) &&
Arrays.equals(parameterTypes, that.parameterTypes);
}

@Override
public int hashCode() {
int result = Objects.hash(className, methodName);
result = 31 * result + Arrays.hashCode(parameterTypes);
return result;
}
}
}
Loading

0 comments on commit 0933b71

Please sign in to comment.