|
2 | 2 |
|
3 | 3 | import java.util.Arrays; |
4 | 4 | import java.util.List; |
| 5 | +import java.util.Set; |
5 | 6 | import java.util.stream.Collectors; |
6 | 7 |
|
7 | 8 | import com.tngtech.archunit.base.DescribedPredicate; |
| 9 | +import com.tngtech.archunit.core.domain.JavaCall; |
8 | 10 | import com.tngtech.archunit.core.domain.JavaClass; |
| 11 | +import com.tngtech.archunit.core.domain.JavaMethod; |
9 | 12 | import com.tngtech.archunit.core.domain.JavaModifier; |
| 13 | +import com.tngtech.archunit.lang.ArchCondition; |
10 | 14 | import com.tngtech.archunit.lang.ArchRule; |
| 15 | +import com.tngtech.archunit.lang.ConditionEvents; |
| 16 | +import com.tngtech.archunit.lang.SimpleConditionEvent; |
11 | 17 |
|
12 | 18 | import org.kohsuke.stapler.DataBoundConstructor; |
13 | 19 | import org.kohsuke.stapler.DataBoundSetter; |
14 | 20 | import org.kohsuke.stapler.bind.JavaScriptMethod; |
15 | 21 | import org.kohsuke.stapler.verb.POST; |
16 | 22 | import hudson.model.AbstractProject; |
17 | 23 | import hudson.model.Descriptor; |
| 24 | +import hudson.util.ComboBoxModel; |
18 | 25 | import hudson.util.FormValidation; |
| 26 | +import hudson.util.ListBoxModel; |
19 | 27 | import jenkins.model.Jenkins; |
20 | 28 |
|
21 | 29 | import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*; |
@@ -89,14 +97,72 @@ public final class PluginArchitectureRules { |
89 | 97 | methods().that().areDeclaredInClassesThat().areAssignableTo(Descriptor.class) |
90 | 98 | .and().haveNameMatching("do[A-Z].*") |
91 | 99 | .and().haveRawReturnType(FormValidation.class) |
92 | | - .and().haveRawParameterTypes(new FormValidationSignaturePredicate()) |
| 100 | + .and().haveRawParameterTypes(ofAllowedValidationMethodSignatures()) |
93 | 101 | .should().beAnnotatedWith(POST.class) |
94 | 102 | .andShould().bePublic(); |
95 | 103 |
|
| 104 | + /** |
| 105 | + * List model methods that are used as AJAX end points must use @POST and have a permission check. |
| 106 | + */ |
| 107 | + public static final ArchRule USE_POST_FOR_LIST_AND_COMBOBOX_FILL = |
| 108 | + methods().that().areDeclaredInClassesThat().areAssignableTo(Descriptor.class) |
| 109 | + .and().haveNameMatching("doFill[A-Z].*") |
| 110 | + .and().haveRawReturnType(ofAllowedClasses(ComboBoxModel.class, ListBoxModel.class)) |
| 111 | + .should().beAnnotatedWith(POST.class) |
| 112 | + .andShould().bePublic() |
| 113 | + .andShould(checkPermissions()); |
| 114 | + |
| 115 | + private static FormValidationSignaturePredicate ofAllowedValidationMethodSignatures() { |
| 116 | + return new FormValidationSignaturePredicate(); |
| 117 | + } |
| 118 | + |
| 119 | + private static HavePermissionCheck checkPermissions() { |
| 120 | + return new HavePermissionCheck(); |
| 121 | + } |
| 122 | + |
| 123 | + private static DescribedPredicate<JavaClass> ofAllowedClasses(final Class<?>... classes) { |
| 124 | + return new AllowedClasses(classes); |
| 125 | + } |
| 126 | + |
96 | 127 | private PluginArchitectureRules() { |
97 | 128 | // prevents instantiation |
98 | 129 | } |
99 | 130 |
|
| 131 | + private static class HavePermissionCheck extends ArchCondition<JavaMethod> { |
| 132 | + HavePermissionCheck() { |
| 133 | + super("should have a permission check"); |
| 134 | + } |
| 135 | + |
| 136 | + @Override |
| 137 | + public void check(final JavaMethod item, final ConditionEvents events) { |
| 138 | + Set<JavaCall<?>> callsFromSelf = item.getCallsFromSelf(); |
| 139 | + |
| 140 | + if (callsFromSelf.stream().anyMatch( |
| 141 | + javaCall -> javaCall.getTarget().getOwner().getFullName().equals(JenkinsFacade.class.getName()) |
| 142 | + && "hasPermission".equals(javaCall.getTarget().getName()))) { |
| 143 | + return; |
| 144 | + } |
| 145 | + events.add(SimpleConditionEvent.violated(item, |
| 146 | + String.format("JenkinsFacade not called in %s in %s", |
| 147 | + item.getDescription(), item.getSourceCodeLocation()))); |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + private static class AllowedClasses extends DescribedPredicate<JavaClass> { |
| 152 | + private final List<String> allowedClassNames; |
| 153 | + |
| 154 | + AllowedClasses(final Class<?>... classes) { |
| 155 | + super("raw return type of any of %s", Arrays.toString(classes)); |
| 156 | + |
| 157 | + allowedClassNames = Arrays.stream(classes).map(Class::getName).collect(Collectors.toList()); |
| 158 | + } |
| 159 | + |
| 160 | + @Override |
| 161 | + public boolean apply(final JavaClass input) { |
| 162 | + return allowedClassNames.contains(input.getFullName()); |
| 163 | + } |
| 164 | + } |
| 165 | + |
100 | 166 | private static class FormValidationSignaturePredicate extends DescribedPredicate<List<JavaClass>> { |
101 | 167 | FormValidationSignaturePredicate() { |
102 | 168 | super("do* method signatures that should be guarded by @POST"); |
|
0 commit comments