diff --git a/CHANGELOG.md b/CHANGELOG.md index be528ab7..c0943d29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.0.0 + +## Improved + +- Added support for groovy 2.5.8 +- Relaxed the sandbox rules to allow more of the Java API to be used in the DSL. This includes some date, calendar, regex and collection APIs. + # 0.8.0 ## Improved diff --git a/build.gradle b/build.gradle index 158f557c..db468f7d 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,7 @@ apply from: "https://raw.githubusercontent.com/gocd/gocd-plugin-gradle-task-help gocdPlugin { id = 'cd.go.contrib.plugins.configrepo.groovy' - pluginVersion = '0.8.0' + pluginVersion = '1.0.0' goCdVersion = '18.3.0' name = 'GoCD Groovy Configuration plugin' description = 'GoCD pipelines and environments configuration in Groovy' @@ -60,5 +60,12 @@ allprojects { subprojects { repositories { mavenCentral() + maven { + url = 'https://repo.jenkins-ci.org/releases' + content { + includeModule "org.kohsuke", 'groovy-sandbox' + } + } } + } diff --git a/cli/build.gradle b/cli/build.gradle index 786dafd4..3142da47 100644 --- a/cli/build.gradle +++ b/cli/build.gradle @@ -20,7 +20,7 @@ dependencies { compile project(':json') compile project(':dsl-validation') compile group: 'com.beust', name: 'jcommander', version: '1.78' - compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.15' + compile group: 'org.codehaus.groovy', name: 'groovy', version: '2.5.8' compile group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.1.0.Final' compile group: 'org.glassfish', name: 'javax.el', version: '3.0.0' } diff --git a/dsl/build.gradle b/dsl/build.gradle index 30b47e04..0f10d4d3 100644 --- a/dsl/build.gradle +++ b/dsl/build.gradle @@ -30,7 +30,7 @@ dependencies { compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.8' - compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.15' + compile group: 'org.codehaus.groovy', name: 'groovy', version: '2.5.8' compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.10.0' compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.9' compile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final' @@ -113,7 +113,7 @@ javadoc { options.overview('overview.html') options.showFromPublic() - options.links('https://docs.oracle.com/javase/9/docs/api', "http://docs.groovy-lang.org/docs/groovy-2.4.15/html/gapi") + options.links('https://docs.oracle.com/javase/9/docs/api', "http://docs.groovy-lang.org/docs/groovy-2.5.8/html/gapi") title = "GoCD Groovy DSL API ${version}" options.footer = 'Copyright (c) 2019 ThoughtWorks, Inc.' } diff --git a/dsl/src/test/java/cd/go/contrib/plugins/configrepo/groovy/dsl/TestAllSignatures.java b/dsl/src/test/java/cd/go/contrib/plugins/configrepo/groovy/dsl/TestAllSignatures.java index c859a503..b129b8a0 100644 --- a/dsl/src/test/java/cd/go/contrib/plugins/configrepo/groovy/dsl/TestAllSignatures.java +++ b/dsl/src/test/java/cd/go/contrib/plugins/configrepo/groovy/dsl/TestAllSignatures.java @@ -50,7 +50,7 @@ void testJSONIndex() { } private static ScanResult scanResult = new ClassGraph() - .verbose() +// .verbose() .enableClassInfo() .enableMethodInfo() .ignoreClassVisibility() diff --git a/gocd-groovy-dsl-config-plugin/build.gradle b/gocd-groovy-dsl-config-plugin/build.gradle index fa8957fe..6a2c764c 100644 --- a/gocd-groovy-dsl-config-plugin/build.gradle +++ b/gocd-groovy-dsl-config-plugin/build.gradle @@ -25,7 +25,7 @@ dependencies { compile project(':cli') compile project(':groovy-export') - compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.15' + compile group: 'org.codehaus.groovy', name: 'groovy', version: '2.5.8' extractedAtTopLevel project(':jar-class-loader') diff --git a/groovy-export/build.gradle b/groovy-export/build.gradle index fb5b6732..8a97b3ab 100644 --- a/groovy-export/build.gradle +++ b/groovy-export/build.gradle @@ -20,7 +20,7 @@ apply plugin: 'maven-publish' dependencies { compile project(':dsl') compile project(':json') - compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.15' + compile group: 'org.codehaus.groovy', name: 'groovy', version: '2.5.8' testCompile project(path: ':json', configuration: 'testArchives') testCompile project(':sandbox') diff --git a/groovy-export/src/main/java/cd/go/contrib/plugins/configrepo/groovy/export/IndentedStringWriter.java b/groovy-export/src/main/java/cd/go/contrib/plugins/configrepo/groovy/export/IndentedStringWriter.java index 2637ded0..5eb7977b 100644 --- a/groovy-export/src/main/java/cd/go/contrib/plugins/configrepo/groovy/export/IndentedStringWriter.java +++ b/groovy-export/src/main/java/cd/go/contrib/plugins/configrepo/groovy/export/IndentedStringWriter.java @@ -28,7 +28,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import static groovy.json.StringEscapeUtils.escapeJava; +import static org.apache.commons.lang3.StringEscapeUtils.escapeJava; import static org.apache.commons.lang3.StringUtils.*; public class IndentedStringWriter { diff --git a/sandbox/build.gradle b/sandbox/build.gradle index 76bb3edb..cd93b29e 100644 --- a/sandbox/build.gradle +++ b/sandbox/build.gradle @@ -15,26 +15,30 @@ */ dependencies { - compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.15' + compile group: 'org.codehaus.groovy', name: 'groovy', version: '2.5.8' + compile group: 'org.codehaus.groovy', name: 'groovy-sql', version: '2.5.8' + compile group: 'org.codehaus.groovy', name: 'groovy-swing', version: '2.5.8' + compile group: 'org.codehaus.groovy', name: 'groovy-dateutil', version: '2.5.8' + compile group: 'org.codehaus.groovy', name: 'groovy-xml', version: '2.5.8' + + compile group: 'com.github.ben-manes.caffeine', name: 'caffeine', version: '2.8.0' compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.9' - compile(group: 'org.kohsuke', name: 'groovy-sandbox', version: '1.19') { + compile(group: 'org.kohsuke', name: 'groovy-sandbox', version: '1.24') { exclude(group: 'org.codehaus.groovy') } - compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.15' - compile(group: 'com.google.guava', name: 'guava', version: '28.1-jre') + compile group: 'com.google.guava', name: 'guava', version: '28.1-jre' + testCompile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.5.8' testCompile group: 'junit', name: 'junit', version: '4.12' + // for @Grab annotation + testCompile group: 'org.apache.ivy', name: 'ivy', version: '2.4.0' testCompile group: 'org.hamcrest', name: 'hamcrest-core', version: '2.2' testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '2.2' testRuntime group: 'org.junit.vintage', name: 'junit-vintage-engine', version: '5.5.2' } tasks.withType(JavaCompile) { - options.deprecation = true - options.encoding = 'utf-8' - options.debug = true - options.warnings = false // since this code that we copied from elsewhere, we disable all lint options.compilerArgs << "-Xlint:-unchecked" } diff --git a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/GroovyCallSiteSelector.java b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/GroovyCallSiteSelector.java index b9db3b85..48c8d2d8 100644 --- a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/GroovyCallSiteSelector.java +++ b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/GroovyCallSiteSelector.java @@ -29,6 +29,8 @@ import org.apache.commons.lang3.ClassUtils; import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -39,13 +41,15 @@ /** * Assists in determination of which method or other JVM element is actually about to be called by Groovy. - * Most of this just duplicates what {@link java.lang.invoke.MethodHandles.Lookup} and {@link java.lang.invoke.MethodHandle#asType} do, + * Most of this just duplicates what {@link java.lang.invoke.MethodHandles.Lookup} and {@link + * java.lang.invoke.MethodHandle#asType} do, * but {@link org.codehaus.groovy.vmplugin.v7.TypeTransformers} shows that there are Groovy-specific complications. - * Comments in https://github.com/kohsuke/groovy-sandbox/issues/7 note that it would be great for the sandbox itself to just tell us what the call site is so we would not have to guess. + * Comments in https://github.com/kohsuke/groovy-sandbox/issues/7 note that it would be great for the sandbox itself to + * just tell us what the call site is so we would not have to guess. */ class GroovyCallSiteSelector { - private static boolean matches(Class[] parameterTypes, Object[] parameters, boolean varargs) { + private static boolean matches(@Nonnull Class[] parameterTypes, @Nonnull Object[] parameters, boolean varargs) { if (varargs) { parameters = parametersForVarargs(parameterTypes, parameters); } @@ -67,8 +71,8 @@ private static boolean matches(Class[] parameterTypes, Object[] parameters, b } if ( parameterTypes[i].isPrimitive() - && parameters[i] != null - && isInstancePrimitive(ClassUtils.primitiveToWrapper(parameterTypes[i]), parameters[i]) + && parameters[i] != null + && isInstancePrimitive(ClassUtils.primitiveToWrapper(parameterTypes[i]), parameters[i]) ) { // Groovy passes primitive values as objects (for example, passes 0 as Integer(0)) // The prior test fails as int.class.isInstance(new Integer(0)) returns false. @@ -86,7 +90,8 @@ && isInstancePrimitive(ClassUtils.primitiveToWrapper(parameterTypes[i]), paramet } /** - * Translates a method parameter list with varargs possibly spliced into the end into the actual parameters to be passed to the JVM call. + * Translates a method parameter list with varargs possibly spliced into the end into the actual parameters to be + * passed to the JVM call. */ private static Object[] parametersForVarargs(Class[] parameterTypes, Object[] parameters) { int fixedLen = parameterTypes.length - 1; @@ -117,7 +122,7 @@ private static Object[] parametersForVarargs(Class[] parameterTypes, Object[] /** * {@link Class#isInstance} extended to handle some important cases of primitive types. */ - private static boolean isInstancePrimitive(Class type, Object instance) { + private static boolean isInstancePrimitive(@Nonnull Class type, @Nonnull Object instance) { if (type.isInstance(instance)) { return true; } @@ -140,11 +145,16 @@ private static boolean isInstancePrimitive(Class type, Object instance) { /** * Looks up the most general possible definition of a given method call. * Preferentially searches for compatible definitions in supertypes. - * @param receiver an actual receiver object - * @param method the method name - * @param args a set of actual arguments + * + * @param receiver + * an actual receiver object + * @param method + * the method name + * @param args + * a set of actual arguments */ - public static Method method(Object receiver, String method, Object[] args) { + public static @CheckForNull + Method method(@Nonnull Object receiver, @Nonnull String method, @Nonnull Object[] args) { for (Class c : types(receiver)) { Method candidate = findMatchingMethod(c, method, args); if (candidate != null) { @@ -160,7 +170,8 @@ public static Method method(Object receiver, String method, Object[] args) { return null; } - public static Constructor constructor(Class receiver, Object[] args) { + public static @CheckForNull + Constructor constructor(@Nonnull Class receiver, @Nonnull Object[] args) { Constructor[] constructors = receiver.getDeclaredConstructors(); Constructor candidate = null; for (Constructor c : constructors) { @@ -188,11 +199,12 @@ public static Constructor constructor(Class receiver, Object[] args) { return null; } - public static Method staticMethod(Class receiver, String method, Object[] args) { + public static @CheckForNull + Method staticMethod(@Nonnull Class receiver, @Nonnull String method, @Nonnull Object[] args) { return findMatchingMethod(receiver, method, args); } - private static Method findMatchingMethod(Class receiver, String method, Object[] args) { + private static Method findMatchingMethod(@Nonnull Class receiver, @Nonnull String method, @Nonnull Object[] args) { Method candidate = null; for (Method m : receiver.getDeclaredMethods()) { @@ -212,7 +224,7 @@ private static Method findMatchingMethod(Class receiver, String method, Objec /** * Emulates, with some tweaks, {@link org.codehaus.groovy.reflection.ParameterTypes#isVargsMethod(Object[])} */ - private static boolean isVarArgsMethod(Method m, Object[] args) { + private static boolean isVarArgsMethod(@Nonnull Method m, @Nonnull Object[] args) { if (m.isVarArgs()) { return true; } @@ -241,7 +253,8 @@ private static boolean isVarArgsMethod(Method m, Object[] args) { return false; } - public static Field field(Object receiver, String field) { + public static @CheckForNull + Field field(@Nonnull Object receiver, @Nonnull String field) { for (Class c : types(receiver)) { for (Field f : c.getDeclaredFields()) { if (f.getName().equals(field)) { @@ -252,7 +265,8 @@ public static Field field(Object receiver, String field) { return null; } - public static Field staticField(Class receiver, String field) { + public static @CheckForNull + Field staticField(@Nonnull Class receiver, @Nonnull String field) { for (Field f : receiver.getDeclaredFields()) { if (f.getName().equals(field)) { return f; @@ -261,12 +275,13 @@ public static Field staticField(Class receiver, String field) { return null; } - private static Iterable> types(Object o) { + private static Iterable> types(@Nonnull Object o) { Set> types = new LinkedHashSet>(); visitTypes(types, o.getClass()); return types; } - private static void visitTypes(Set> types, Class c) { + + private static void visitTypes(@Nonnull Set> types, @Nonnull Class c) { Class s = c.getSuperclass(); if (s != null) { visitTypes(types, s); @@ -307,6 +322,7 @@ private static boolean isMoreSpecific(AccessibleObject more, Class[] morePara return more.toString().compareTo(less.toString()) > 0; } - private GroovyCallSiteSelector() {} + private GroovyCallSiteSelector() { + } } diff --git a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/GroovySandbox.java b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/GroovySandbox.java index af947b39..7c511b5e 100644 --- a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/GroovySandbox.java +++ b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/GroovySandbox.java @@ -27,17 +27,22 @@ import cd.go.contrib.plugins.configrepo.groovy.sandbox.whitelists.ClassLoaderWhitelist; import cd.go.contrib.plugins.configrepo.groovy.sandbox.whitelists.ProxyWhitelist; import cd.go.contrib.plugins.configrepo.groovy.sandbox.whitelists.Whitelist; +import groovy.grape.GrabAnnotationTransformation; import groovy.lang.Script; import org.codehaus.groovy.control.CompilerConfiguration; import org.kohsuke.groovy.sandbox.GroovyInterceptor; import org.kohsuke.groovy.sandbox.SandboxTransformer; +import java.util.Collections; +import java.util.HashSet; import java.util.concurrent.Callable; public class GroovySandbox { public static CompilerConfiguration createSecureCompilerConfiguration() { CompilerConfiguration cc = new CompilerConfiguration(); + cc.addCompilationCustomizers(new RejectASTTransformsCustomizer()); + cc.setDisabledGlobalASTTransformations(new HashSet<>(Collections.singletonList(GrabAnnotationTransformation.class.getName()))); cc.addCompilationCustomizers(new SandboxTransformer()); return cc; } diff --git a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/RejectASTTransformsCustomizer.java b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/RejectASTTransformsCustomizer.java new file mode 100644 index 00000000..4e57884e --- /dev/null +++ b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/RejectASTTransformsCustomizer.java @@ -0,0 +1,112 @@ +/* + * Copyright 2019 ThoughtWorks, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cd.go.contrib.plugins.configrepo.groovy.sandbox; + +import com.google.common.collect.ImmutableList; +import groovy.lang.*; +import groovy.transform.ASTTest; +import groovy.transform.AnnotationCollector; +import org.codehaus.groovy.ast.*; +import org.codehaus.groovy.classgen.GeneratorContext; +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.CompilePhase; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.control.customizers.CompilationCustomizer; + +import java.util.ArrayList; +import java.util.List; + +public class RejectASTTransformsCustomizer extends CompilationCustomizer { + private static final List BLOCKED_TRANSFORMS = ImmutableList.of(ASTTest.class.getCanonicalName(), Grab.class.getCanonicalName(), + GrabConfig.class.getCanonicalName(), GrabExclude.class.getCanonicalName(), GrabResolver.class.getCanonicalName(), + Grapes.class.getCanonicalName(), AnnotationCollector.class.getCanonicalName()); + + public RejectASTTransformsCustomizer() { + super(CompilePhase.CONVERSION); + } + + @Override + public void call(final SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException { + new RejectASTTransformsVisitor(source).visitClass(classNode); + } + + private static class RejectASTTransformsVisitor extends ClassCodeVisitorSupport { + private SourceUnit source; + + public RejectASTTransformsVisitor(SourceUnit source) { + this.source = source; + } + + @Override + protected SourceUnit getSourceUnit() { + return source; + } + + @Override + public void visitImports(ModuleNode node) { + if (node != null) { + for (ImportNode importNode : node.getImports()) { + checkImportForBlockedAnnotation(importNode); + } + for (ImportNode importStaticNode : node.getStaticImports().values()) { + checkImportForBlockedAnnotation(importStaticNode); + } + } + } + + private void checkImportForBlockedAnnotation(ImportNode node) { + if (node != null && node.getType() != null) { + for (String blockedAnnotation : getBlockedTransforms()) { + if (blockedAnnotation.equals(node.getType().getName()) || blockedAnnotation.endsWith("." + node.getType().getName())) { + throw new SecurityException("Annotation " + node.getType().getName() + " cannot be used in the sandbox."); + } + } + } + } + + /** + * If the node is annotated with one of the blocked transform annotations, throw a security exception. + * + * @param node the node to process + */ + @Override + public void visitAnnotations(AnnotatedNode node) { + for (AnnotationNode an : node.getAnnotations()) { + for (String blockedAnnotation : getBlockedTransforms()) { + if (blockedAnnotation.equals(an.getClassNode().getName()) || blockedAnnotation.endsWith("." + an.getClassNode().getName())) { + throw new SecurityException("Annotation " + an.getClassNode().getName() + " cannot be used in the sandbox."); + } + } + } + } + } + + private static List getBlockedTransforms() { + List blocked = new ArrayList<>(BLOCKED_TRANSFORMS); + + String additionalBlocked = System.getProperty(RejectASTTransformsCustomizer.class.getName() + ".ADDITIONAL_BLOCKED_TRANSFORMS"); + + if (additionalBlocked != null) { + for (String b : additionalBlocked.split(",")) { + blocked.add(b.trim()); + } + } + + return blocked; + } +} + diff --git a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/RejectedAccessException.java b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/RejectedAccessException.java index 64006b64..7c9bcf33 100644 --- a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/RejectedAccessException.java +++ b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/RejectedAccessException.java @@ -27,22 +27,44 @@ import cd.go.contrib.plugins.configrepo.groovy.sandbox.whitelists.StaticWhitelist; +import javax.annotation.CheckForNull; + +/** + * Thrown when access to a language element was not permitted. + * @see GroovySandbox#runInSandbox(Runnable, Whitelist) + */ public final class RejectedAccessException extends SecurityException { private final String signature; - private boolean dangerous; + /** + * Rejects access to a well-described script element. + * Normally called from {@link StaticWhitelist#rejectMethod} or similar. + * @param type e.g. {@code field} + * @param details e.g. {@code some.Class fieldName} + */ public RejectedAccessException(String type, String details) { super("Scripts not permitted to use " + type + " " + details); signature = type + " " + details; } + /** + * Rejects access to a well-described script element. + * Normally called from {@link StaticWhitelist#rejectMethod} or similar. + * @param type e.g. {@code field} + * @param details e.g. {@code some.Class fieldName} + * @param info some additional information if appropriate + */ public RejectedAccessException(String type, String details, String info) { super("Scripts not permitted to use " + type + " " + details + " (" + info + ")"); signature = type + " " + details; } + /** + * Rejects access to something which the current {@link StaticWhitelist} format could not describe. + * @param message a descriptive message in no particular format + */ public RejectedAccessException(String message) { super(message); signature = null; @@ -50,17 +72,15 @@ public RejectedAccessException(String message) { /** * Gets the signature of the member to which access was rejected. - * - * @return a line in the format understood by {@link StaticWhitelist}, or null in case something was rejected for - * which a known exemption is not available + * @return a line in the format understood by {@link StaticWhitelist}, or null in case something was rejected for which a known exemption is not available */ - public String getSignature() { + public @CheckForNull + String getSignature() { return signature; } /** * True if {@link #getSignature} is non-null but it would be a bad idea for an administrator to approve it. - * * @since 1.16 */ public boolean isDangerous() { @@ -69,10 +89,7 @@ public boolean isDangerous() { /** * You may set this flag if you think it would be a security risk for this signature to be approved. - * - * @throws IllegalArgumentException - * in case you tried to set this to true when using the nonspecific {@link #RejectedAccessException(String)} - * constructor + * @throws IllegalArgumentException in case you tried to set this to true when using the nonspecific {@link #RejectedAccessException(String)} constructor * @since 1.16 */ public void setDangerous(boolean dangerous) { diff --git a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/SandboxInterceptor.java b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/SandboxInterceptor.java index b2056b9f..3db08025 100644 --- a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/SandboxInterceptor.java +++ b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/SandboxInterceptor.java @@ -28,18 +28,30 @@ import cd.go.contrib.plugins.configrepo.groovy.sandbox.whitelists.StaticWhitelist; import cd.go.contrib.plugins.configrepo.groovy.sandbox.whitelists.Whitelist; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import groovy.lang.*; import org.apache.commons.lang3.StringUtils; +import org.apache.groovy.dateutil.extensions.DateUtilExtensions; +import org.apache.groovy.dateutil.extensions.DateUtilStaticExtensions; +import org.apache.groovy.sql.extensions.SqlExtensions; import org.codehaus.groovy.runtime.*; +import org.codehaus.groovy.runtime.m12n.ExtensionModule; +import org.codehaus.groovy.runtime.m12n.ExtensionModuleRegistry; +import org.codehaus.groovy.runtime.m12n.SimpleExtensionModule; import org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod; +import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl; import org.codehaus.groovy.runtime.typehandling.NumberMathModificationInfo; import org.codehaus.groovy.tools.DgmConverter; import org.kohsuke.groovy.sandbox.GroovyInterceptor; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -56,21 +68,45 @@ final class SandboxInterceptor extends GroovyInterceptor { } /** should be synchronized with {@link DgmConverter} */ - private static final Class[] DGM_CLASSES = { - DefaultGroovyMethods.class, - StringGroovyMethods.class, - SwingGroovyMethods.class, - SqlGroovyMethods.class, - XmlGroovyMethods.class, - EncodingGroovyMethods.class, - DateGroovyMethods.class, - ProcessGroovyMethods.class, - }; + private static final Set DGM_CLASSES = Sets.newHashSet( + DefaultGroovyMethods.class, + StringGroovyMethods.class, + SwingGroovyMethods.class, + SqlGroovyMethods.class, + SqlExtensions.class, + XmlGroovyMethods.class, + EncodingGroovyMethods.class, + DateGroovyMethods.class, + DateUtilExtensions.class, + DateUtilStaticExtensions.class, + ProcessGroovyMethods.class + ); + + static { + MetaClassRegistryImpl metaClassRegistry = (MetaClassRegistryImpl) GroovySystem.getMetaClassRegistry(); + + ExtensionModuleRegistry moduleRegistry = metaClassRegistry.getModuleRegistry(); + List modules = moduleRegistry.getModules(); + for (ExtensionModule module : modules) { + if (module instanceof SimpleExtensionModule) { + List instanceMethodsExtensionClasses = ((SimpleExtensionModule) module).getInstanceMethodsExtensionClasses(); + if (instanceMethodsExtensionClasses != null) { + DGM_CLASSES.addAll(instanceMethodsExtensionClasses); + } + + List staticMethodsExtensionClasses = ((SimpleExtensionModule) module).getStaticMethodsExtensionClasses(); + if (staticMethodsExtensionClasses != null) { + DGM_CLASSES.addAll(staticMethodsExtensionClasses); + } + } + } + } /** @see NumberMathModificationInfo */ private static final Set NUMBER_MATH_NAMES = ImmutableSet.of("plus", "minus", "multiply", "div", "compareTo", "or", "and", "xor", "intdiv", "mod", "leftShift", "rightShift", "rightShiftUnsigned"); - @Override public Object onMethodCall(GroovyInterceptor.Invoker invoker, Object receiver, String method, Object... args) throws Throwable { + @Override + public Object onMethodCall(GroovyInterceptor.Invoker invoker, Object receiver, String method, Object... args) throws Throwable { Method m = GroovyCallSiteSelector.method(receiver, method, args); if (m == null) { if (receiver instanceof Number && NUMBER_MATH_NAMES.contains(method)) { @@ -101,10 +137,29 @@ final class SandboxInterceptor extends GroovyInterceptor { throw StaticWhitelist.rejectStaticMethod(foundDgmMethod); } + // allow calling Closure elements of Maps as methods + if (receiver instanceof Map) { + Object element = onMethodCall(invoker, receiver, "get", method); + if (element instanceof Closure) { + return onMethodCall(invoker, element, "call", args); + } + } + + // Allow calling closure variables from a script binding as methods + if (receiver instanceof Script) { + Script s = (Script) receiver; + if (s.getBinding().hasVariable(method)) { + Object var = s.getBinding().getVariable(method); + if (!InvokerHelper.getMetaClass(var).respondsTo(var, "call", (Object[]) args).isEmpty()) { + return onMethodCall(invoker, var, "call", args); + } + } + } + // if no matching method, look for catchAll "invokeMethod" try { receiver.getClass().getMethod("invokeMethod", String.class, Object.class); - return onMethodCall(invoker,receiver,"invokeMethod",method,args); + return onMethodCall(invoker, receiver, "invokeMethod", method, args); } catch (NoSuchMethodException e) { // fall through } @@ -116,6 +171,8 @@ final class SandboxInterceptor extends GroovyInterceptor { // no such method exists throw new MissingMethodException(method, receiver.getClass(), args); + } else if (StaticWhitelist.isPermanentlyBlacklistedMethod(m)) { + throw StaticWhitelist.rejectMethod(m); } else if (whitelist.permitsMethod(m, receiver, args)) { return super.onMethodCall(invoker, receiver, method, args); } else if (method.equals("invokeMethod") && args.length == 2 && args[0] instanceof String && args[1] instanceof Object[]) { @@ -125,10 +182,13 @@ final class SandboxInterceptor extends GroovyInterceptor { } } - @Override public Object onNewInstance(GroovyInterceptor.Invoker invoker, Class receiver, Object... args) throws Throwable { + @Override + public Object onNewInstance(GroovyInterceptor.Invoker invoker, Class receiver, Object... args) throws Throwable { Constructor c = GroovyCallSiteSelector.constructor(receiver, args); if (c == null) { - throw new RejectedAccessException("unclassified new " + EnumeratingWhitelist.getName(receiver) + printArgumentTypes(args)); + throw new RejectedAccessException("No such constructor found: new " + EnumeratingWhitelist.getName(receiver) + printArgumentTypes(args)); + } else if (StaticWhitelist.isPermanentlyBlacklistedConstructor(c)) { + throw StaticWhitelist.rejectNew(c); } else if (whitelist.permitsConstructor(c, args)) { return super.onNewInstance(invoker, receiver, args); } else { @@ -136,11 +196,14 @@ final class SandboxInterceptor extends GroovyInterceptor { } } - @Override public Object onStaticCall(GroovyInterceptor.Invoker invoker, Class receiver, String method, Object... args) throws Throwable { + @Override + public Object onStaticCall(GroovyInterceptor.Invoker invoker, Class receiver, String method, Object... args) throws Throwable { Method m = GroovyCallSiteSelector.staticMethod(receiver, method, args); if (m == null) { // TODO consider DefaultGroovyStaticMethods - throw new RejectedAccessException("unclassified staticMethod " + EnumeratingWhitelist.getName(receiver) + " " + method + printArgumentTypes(args)); + throw new RejectedAccessException("No such static method found: staticMethod " + EnumeratingWhitelist.getName(receiver) + " " + method + printArgumentTypes(args)); + } else if (StaticWhitelist.isPermanentlyBlacklistedStaticMethod(m)) { + throw StaticWhitelist.rejectStaticMethod(m); } else if (whitelist.permitsStaticMethod(m, args)) { return super.onStaticCall(invoker, receiver, method, args); } else { @@ -148,13 +211,14 @@ final class SandboxInterceptor extends GroovyInterceptor { } } - @Override public Object onSetProperty(GroovyInterceptor.Invoker invoker, final Object receiver, final String property, Object value) throws Throwable { + @Override + public Object onSetProperty(GroovyInterceptor.Invoker invoker, final Object receiver, final String property, Object value) throws Throwable { if (receiver instanceof Script && !property.equals("binding") && !property.equals("metaClass")) { return super.onSetProperty(invoker, receiver, property, value); } Rejector rejector = null; // avoid creating exception objects unless and until thrown // https://github.com/kohsuke/groovy-sandbox/issues/7 need to explicitly check for getters and setters: - Object[] valueArg = new Object[] {value}; + Object[] valueArg = new Object[]{value}; String setter = "set" + StringUtils.capitalize(property); final Method setterMethod = GroovyCallSiteSelector.method(receiver, setter, valueArg); if (setterMethod != null) { @@ -162,20 +226,22 @@ final class SandboxInterceptor extends GroovyInterceptor { return super.onSetProperty(invoker, receiver, property, value); } else if (rejector == null) { rejector = new Rejector() { - @Override public RejectedAccessException reject() { + @Override + public RejectedAccessException reject() { return StaticWhitelist.rejectMethod(setterMethod); } }; } } - Object[] propertyValueArgs = new Object[] {property, value}; + Object[] propertyValueArgs = new Object[]{property, value}; final Method setPropertyMethod = GroovyCallSiteSelector.method(receiver, "setProperty", propertyValueArgs); if (setPropertyMethod != null) { if (whitelist.permitsMethod(setPropertyMethod, receiver, propertyValueArgs)) { return super.onSetProperty(invoker, receiver, property, value); } else if (rejector == null) { rejector = new Rejector() { - @Override public RejectedAccessException reject() { + @Override + public RejectedAccessException reject() { return StaticWhitelist.rejectMethod(setPropertyMethod, receiver.getClass().getName() + "." + property); } }; @@ -187,7 +253,8 @@ final class SandboxInterceptor extends GroovyInterceptor { return super.onSetProperty(invoker, receiver, property, value); } else if (rejector == null) { rejector = new Rejector() { - @Override public RejectedAccessException reject() { + @Override + public RejectedAccessException reject() { return StaticWhitelist.rejectField(instanceField); } }; @@ -200,7 +267,8 @@ final class SandboxInterceptor extends GroovyInterceptor { return super.onSetProperty(invoker, receiver, property, value); } else if (rejector == null) { rejector = new Rejector() { - @Override public RejectedAccessException reject() { + @Override + public RejectedAccessException reject() { return StaticWhitelist.rejectStaticMethod(staticSetterMethod); } }; @@ -212,7 +280,8 @@ final class SandboxInterceptor extends GroovyInterceptor { return super.onSetProperty(invoker, receiver, property, value); } else if (rejector == null) { rejector = new Rejector() { - @Override public RejectedAccessException reject() { + @Override + public RejectedAccessException reject() { return StaticWhitelist.rejectStaticField(staticField); } }; @@ -222,7 +291,8 @@ final class SandboxInterceptor extends GroovyInterceptor { throw rejector != null ? rejector.reject() : unclassifiedField(receiver, property); } - @Override public Object onGetProperty(GroovyInterceptor.Invoker invoker, final Object receiver, final String property) throws Throwable { + @Override + public Object onGetProperty(GroovyInterceptor.Invoker invoker, final Object receiver, final String property) throws Throwable { MissingPropertyException mpe = null; if (receiver instanceof Script) { // SimpleTemplateEngine "out" variable, and anything else added in a binding try { @@ -236,7 +306,7 @@ final class SandboxInterceptor extends GroovyInterceptor { return super.onGetProperty(invoker, receiver, property); } Rejector rejector = null; - Object[] noArgs = new Object[] {}; + Object[] noArgs = new Object[]{}; String getter = "get" + StringUtils.capitalize(property); final Method getterMethod = GroovyCallSiteSelector.method(receiver, getter, noArgs); if (getterMethod != null) { @@ -244,7 +314,8 @@ final class SandboxInterceptor extends GroovyInterceptor { return super.onGetProperty(invoker, receiver, property); } else if (rejector == null) { rejector = new Rejector() { - @Override public RejectedAccessException reject() { + @Override + public RejectedAccessException reject() { return StaticWhitelist.rejectMethod(getterMethod); } }; @@ -257,14 +328,15 @@ final class SandboxInterceptor extends GroovyInterceptor { return super.onGetProperty(invoker, receiver, property); } else if (rejector == null) { rejector = new Rejector() { - @Override public RejectedAccessException reject() { + @Override + public RejectedAccessException reject() { return StaticWhitelist.rejectMethod(booleanGetterMethod); } }; } } // look for GDK methods - Object[] selfArgs = new Object[] {receiver}; + Object[] selfArgs = new Object[]{receiver}; for (Class dgmClass : DGM_CLASSES) { final Method dgmGetterMethod = GroovyCallSiteSelector.staticMethod(dgmClass, getter, selfArgs); if (dgmGetterMethod != null) { @@ -299,21 +371,23 @@ public RejectedAccessException reject() { return super.onGetProperty(invoker, receiver, property); } else if (rejector == null) { rejector = new Rejector() { - @Override public RejectedAccessException reject() { + @Override + public RejectedAccessException reject() { return StaticWhitelist.rejectField(instanceField); } }; } } // GroovyObject property access - Object[] propertyArg = new Object[] {property}; + Object[] propertyArg = new Object[]{property}; final Method getPropertyMethod = GroovyCallSiteSelector.method(receiver, "getProperty", propertyArg); if (getPropertyMethod != null) { if (whitelist.permitsMethod(getPropertyMethod, receiver, propertyArg)) { return super.onGetProperty(invoker, receiver, property); } else if (rejector == null) { rejector = new Rejector() { - @Override public RejectedAccessException reject() { + @Override + public RejectedAccessException reject() { return StaticWhitelist.rejectMethod(getPropertyMethod, receiver.getClass().getName() + "." + property); } }; @@ -331,7 +405,8 @@ public RejectedAccessException reject() { return super.onGetProperty(invoker, receiver, property); } else if (rejector == null) { rejector = new Rejector() { - @Override public RejectedAccessException reject() { + @Override + public RejectedAccessException reject() { return StaticWhitelist.rejectStaticMethod(staticGetterMethod); } }; @@ -343,7 +418,8 @@ public RejectedAccessException reject() { return super.onGetProperty(invoker, receiver, property); } else if (rejector == null) { rejector = new Rejector() { - @Override public RejectedAccessException reject() { + @Override + public RejectedAccessException reject() { return StaticWhitelist.rejectStaticMethod(staticBooleanGetterMethod); } }; @@ -355,7 +431,8 @@ public RejectedAccessException reject() { return super.onGetProperty(invoker, receiver, property); } else if (rejector == null) { rejector = new Rejector() { - @Override public RejectedAccessException reject() { + @Override + public RejectedAccessException reject() { return StaticWhitelist.rejectStaticField(staticField); } }; @@ -372,7 +449,7 @@ public RejectedAccessException reject() { public Object onSuperCall(Invoker invoker, Class senderType, Object receiver, String method, Object... args) throws Throwable { Method m = GroovyCallSiteSelector.method(receiver, method, args); if (m == null) { - throw new RejectedAccessException("unclassified super.method " + EnumeratingWhitelist.getName(receiver.getClass()) + " " + method + printArgumentTypes(args)); + throw new RejectedAccessException("No such method found: super.method " + EnumeratingWhitelist.getName(receiver.getClass()) + " " + method + printArgumentTypes(args)); } else if (whitelist.permitsMethod(m, receiver, args)) { return super.onSuperCall(invoker, senderType, receiver, method, args); } else { @@ -381,15 +458,18 @@ public Object onSuperCall(Invoker invoker, Class senderType, Object receiver, St } private static RejectedAccessException unclassifiedField(Object receiver, String property) { - return new RejectedAccessException("unclassified field " + EnumeratingWhitelist.getName(receiver.getClass()) + " " + property); + return new RejectedAccessException("No such field found: field " + EnumeratingWhitelist.getName(receiver.getClass()) + " " + property); } // TODO Java 8: @FunctionalInterface private interface Rejector { + + @Nonnull RejectedAccessException reject(); } - @Override public Object onGetAttribute(Invoker invoker, Object receiver, String attribute) throws Throwable { + @Override + public Object onGetAttribute(Invoker invoker, Object receiver, String attribute) throws Throwable { Field field = GroovyCallSiteSelector.field(receiver, attribute); if (field == null) { throw unclassifiedField(receiver, attribute); @@ -400,7 +480,8 @@ private interface Rejector { } } - @Override public Object onSetAttribute(Invoker invoker, Object receiver, String attribute, Object value) throws Throwable { + @Override + public Object onSetAttribute(Invoker invoker, Object receiver, String attribute, Object value) throws Throwable { Field field = GroovyCallSiteSelector.field(receiver, attribute); if (field == null) { throw unclassifiedField(receiver, attribute); @@ -411,11 +492,12 @@ private interface Rejector { } } - @Override public Object onGetArray(Invoker invoker, Object receiver, Object index) throws Throwable { + @Override + public Object onGetArray(Invoker invoker, Object receiver, Object index) throws Throwable { if (receiver.getClass().isArray() && index instanceof Integer) { return super.onGetArray(invoker, receiver, index); } - Object[] args = new Object[] {index}; + Object[] args = new Object[]{index}; Method method = GroovyCallSiteSelector.method(receiver, "getAt", args); if (method != null) { if (whitelist.permitsMethod(method, receiver, args)) { @@ -424,7 +506,7 @@ private interface Rejector { throw StaticWhitelist.rejectMethod(method); } } - args = new Object[] {receiver, index}; + args = new Object[]{receiver, index}; for (Class dgm : DGM_CLASSES) { method = GroovyCallSiteSelector.staticMethod(dgm, "getAt", args); if (method != null) { @@ -435,14 +517,15 @@ private interface Rejector { } } } - throw new RejectedAccessException("unclassified getAt method " + EnumeratingWhitelist.getName(receiver) + "[" + EnumeratingWhitelist.getName(index) + "]"); + throw new RejectedAccessException("No such getAt method found: method " + EnumeratingWhitelist.getName(receiver) + "[" + EnumeratingWhitelist.getName(index) + "]"); } - @Override public Object onSetArray(Invoker invoker, Object receiver, Object index, Object value) throws Throwable { + @Override + public Object onSetArray(Invoker invoker, Object receiver, Object index, Object value) throws Throwable { if (receiver.getClass().isArray() && index instanceof Integer) { return super.onSetArray(invoker, receiver, index, value); } - Object[] args = new Object[] {index, value}; + Object[] args = new Object[]{index, value}; Method method = GroovyCallSiteSelector.method(receiver, "putAt", args); if (method != null) { if (whitelist.permitsMethod(method, receiver, args)) { @@ -451,7 +534,7 @@ private interface Rejector { throw StaticWhitelist.rejectMethod(method); } } - args = new Object[] {receiver, index, value}; + args = new Object[]{receiver, index, value}; for (Class dgm : DGM_CLASSES) { method = GroovyCallSiteSelector.staticMethod(dgm, "putAt", args); if (method != null) { @@ -462,7 +545,7 @@ private interface Rejector { } } } - throw new RejectedAccessException("unclassified putAt method " + EnumeratingWhitelist.getName(receiver) + "[" + EnumeratingWhitelist.getName(index) + "]=" + EnumeratingWhitelist.getName(value)); + throw new RejectedAccessException("No such putAt method found: putAt method " + EnumeratingWhitelist.getName(receiver) + "[" + EnumeratingWhitelist.getName(index) + "]=" + EnumeratingWhitelist.getName(value)); } private static String printArgumentTypes(Object[] args) { @@ -474,7 +557,8 @@ private static String printArgumentTypes(Object[] args) { return b.toString(); } - private static MetaMethod findMetaMethod(Object receiver, String method, Object[] args) { + private static @CheckForNull + MetaMethod findMetaMethod(@Nonnull Object receiver, @Nonnull String method, @Nonnull Object[] args) { Class[] types = new Class[args.length]; for (int i = 0; i < types.length; i++) { Object arg = args[i]; diff --git a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/SandboxResolvingClassLoader.java b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/SandboxResolvingClassLoader.java index 39bf6ead..dfaa0740 100644 --- a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/SandboxResolvingClassLoader.java +++ b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/SandboxResolvingClassLoader.java @@ -16,22 +16,22 @@ package cd.go.contrib.plugins.configrepo.groovy.sandbox; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import groovy.util.GroovyScriptEngine; - +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import groovy.lang.GroovyShell; import java.net.URL; -import java.util.concurrent.TimeUnit; +import java.time.Duration; +import java.util.Optional; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; /** * Makes sure that the class references to groovy-sandbox resolves to our own copy of * groovy-sandbox.jar instead of the random one picked up by the classloader - * given to {@link GroovyScriptEngine} via the constructor. + * given to {@link GroovyShell} via the constructor. *

Also tries to cache parent class loading calls, to work around various issues including lack of parallelism. - * * @see JENKINS-25438 * @see JENKINS-23784 */ @@ -39,45 +39,44 @@ class SandboxResolvingClassLoader extends ClassLoader { private static final Logger LOGGER = Logger.getLogger(SandboxResolvingClassLoader.class.getName()); - @SuppressWarnings("rawtypes") - private static final LoadingCache>> parentClassCache = makeParentCache(new CacheFunction>() { - @Override - public java.util.Optional compute(ClassLoader parentLoader, String name) { - try { - Class c = parentLoader.loadClass(name); - return java.util.Optional.of(c); - } catch (ClassNotFoundException x) { - return java.util.Optional.empty(); - } - } - }); + static final LoadingCache>> parentClassCache = makeParentCache(true); - private static final LoadingCache>> parentResourceCache = makeParentCache(new CacheFunction>() { - @Override - public java.util.Optional compute(ClassLoader parentLoader, String name) { - return java.util.Optional.ofNullable(parentLoader.getResource(name)); - } - }); + static final LoadingCache>> parentResourceCache = makeParentCache(false); SandboxResolvingClassLoader(ClassLoader parent) { super(parent); } - @Override - protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + /** + * Marker value for a {@link ClassNotFoundException} negative cache hit. + * Cannot use null, since the cache API does not permit null values. + * Cannot use {@code Optional>} since weak values would mean this is always collected. + * This value is non-null, not a legitimate return value + * (no script should be trying to load this implementation detail), and strongly held. + */ + static final Class CLASS_NOT_FOUND = Unused.class; + private static final class Unused {} + + @Override protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { if (name.startsWith("org.kohsuke.groovy.sandbox.")) { return this.getClass().getClassLoader().loadClass(name); } else { - Class c = parentClassCache.getUnchecked(getParent()).getUnchecked(name).orElse(null); - if (c != null) { + ClassLoader parentLoader = getParent(); + Class c = load(parentClassCache, name, parentLoader, () -> { + try { + return parentLoader.loadClass(name); + } catch (ClassNotFoundException x) { + return CLASS_NOT_FOUND; + } + }); + if (c != CLASS_NOT_FOUND) { if (resolve) { super.resolveClass(c); } return c; } else { throw new ClassNotFoundException(name) { - @Override - public synchronized Throwable fillInStackTrace() { + @Override public synchronized Throwable fillInStackTrace() { return this; // super call is too expensive } }; @@ -85,40 +84,45 @@ public synchronized Throwable fillInStackTrace() { } } - @Override - public URL getResource(String name) { - return parentResourceCache.getUnchecked(getParent()).getUnchecked(name).orElse(null); + @Override public URL getResource(String name) { + ClassLoader parentLoader = getParent(); + return load(parentResourceCache, name, parentLoader, () -> Optional.ofNullable(parentLoader.getResource(name))).orElse(null); } - private interface CacheFunction { - - T compute(ClassLoader parentLoader, String name); - } - - private static LoadingCache> makeParentCache(final CacheFunction function) { - return CacheBuilder.newBuilder().weakKeys().build(new CacheLoader>() { - @Override - public LoadingCache load(final ClassLoader parentLoader) { - return CacheBuilder.newBuilder()./* allow new plugins to be used, and clean up memory */expireAfterWrite(15, TimeUnit.MINUTES).build(new CacheLoader() { - @Override - public T load(String name) { - Thread t = Thread.currentThread(); - String origName = t.getName(); - t.setName(origName + " loading " + name); - long start = System.nanoTime(); // http://stackoverflow.com/q/19052316/12916 - try { - return function.compute(parentLoader, name); - } finally { - t.setName(origName); - long ms = (System.nanoTime() - start) / 1000000; - if (ms > 250) { - LOGGER.log(Level.WARNING, "took {0}ms to load/not load {1} from {2}", new Object[]{ms, name, parentLoader}); - } - } - } - }); + // We cannot have the inner cache be a LoadingCache and just use .get(name), since then the values of the outer cache would strongly refer to the keys. + private static T load(LoadingCache> cache, String name, ClassLoader parentLoader, Supplier supplier) { + // itemName is ignored but caffeine requires a function + return cache.get(parentLoader).get(name, (String itemName) -> { + Thread t = Thread.currentThread(); + String origName = t.getName(); + t.setName(origName + " loading " + name); + long start = System.nanoTime(); // http://stackoverflow.com/q/19052316/12916 + try { + return supplier.get(); + } finally { + t.setName(origName); + long ms = (System.nanoTime() - start) / 1000000; + if (ms > 1000) { + LOGGER.log(Level.INFO, "took {0}ms to load/not load {1} from {2}", new Object[] {ms, name, parentLoader}); + } } }); } + private static LoadingCache> makeParentCache(boolean weakValuesInnerCache) { + // The outer cache has weak keys, so that we do not leak class loaders, but strong values, because the + // inner caches are only referenced by the outer cache internally. + Caffeine outerBuilder = Caffeine.newBuilder().recordStats().weakKeys(); + // The inner cache has strong keys, since they are just strings, and expires entries 15 minutes after they are + // added to the cache, so that classes defined by dynamically installed plugins become available even if there + // were negative cache hits prior to the installation (ideally this would be done with a listener). The values + // for the inner cache may be weak if needed, for example parentClassCache uses weak values to avoid leaking + // classes and their loaders. + Caffeine innerBuilder = Caffeine.newBuilder().recordStats().expireAfterWrite(Duration.ofMinutes(15)); + if (weakValuesInnerCache) { + innerBuilder.weakValues(); + } + + return outerBuilder.build(parentLoader -> innerBuilder.build()); + } } diff --git a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/ClassLoaderWhitelist.java b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/ClassLoaderWhitelist.java index 259b79fd..7ac5687a 100644 --- a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/ClassLoaderWhitelist.java +++ b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/ClassLoaderWhitelist.java @@ -64,3 +64,4 @@ private boolean permits(Class declaringClass) { return permits(field.getDeclaringClass()); } } + diff --git a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/EnumeratingWhitelist.java b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/EnumeratingWhitelist.java index 9424da6e..4ff61340 100644 --- a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/EnumeratingWhitelist.java +++ b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/EnumeratingWhitelist.java @@ -26,6 +26,8 @@ import org.apache.commons.lang3.ClassUtils; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -72,8 +74,8 @@ final void precache() { this.permittedCache.clear(); // No sense calling clearCache } cacheSignatureList((List)methodSignatures(), (List)(newSignatures()), - (List)(staticMethodSignatures()), (List)(fieldSignatures()), - (List)(staticFieldSignatures())); + (List)(staticMethodSignatures()), (List)(fieldSignatures()), + (List)(staticFieldSignatures())); } /** Frees up nearly all memory used for the cache. MUST BE CALLED if you change the result of the xxSignatures() methods. */ @@ -180,7 +182,8 @@ final void clearCache() { return permitsStaticFieldGet(field); } - public static String getName(Class c) { + public static @Nonnull + String getName(@Nonnull Class c) { Class e = c.getComponentType(); if (e == null) { return c.getName(); @@ -189,7 +192,7 @@ public static String getName(Class c) { } } - public static String getName(Object o) { + public static @Nonnull String getName(@CheckForNull Object o) { return o == null ? "null" : getName(o.getClass()); } @@ -246,37 +249,37 @@ static String[] argumentTypes(Class[] argumentTypes) { } /** Canonical name for a field access. */ - static String canonicalFieldString(Field field) { + static String canonicalFieldString(@Nonnull Field field) { return getName(field.getDeclaringClass()) + ' ' + field.getName(); } /** Canonical name for a method call. */ - static String canonicalMethodString(Method method) { + static String canonicalMethodString(@Nonnull Method method) { return joinWithSpaces(new StringBuilder(getName(method.getDeclaringClass())).append(' ').append(method.getName()), argumentTypes(method.getParameterTypes())).toString(); } /** Canonical name for a constructor call. */ - static String canonicalConstructorString(Constructor cons) { + static String canonicalConstructorString(@Nonnull Constructor cons) { return joinWithSpaces(new StringBuilder(getName(cons.getDeclaringClass())), argumentTypes(cons.getParameterTypes())).toString(); } - static String canonicalMethodSig(Method method) { + static String canonicalMethodSig(@Nonnull Method method) { return "method "+canonicalMethodString(method); } - static String canonicalStaticMethodSig(Method method) { + static String canonicalStaticMethodSig(@Nonnull Method method) { return "staticMethod "+canonicalMethodString(method); } - static String canonicalConstructorSig(Constructor cons) { + static String canonicalConstructorSig(@Nonnull Constructor cons) { return "new "+canonicalConstructorString(cons); } - static String canonicalFieldSig(Field field) { + static String canonicalFieldSig(@Nonnull Field field) { return "field "+canonicalFieldString(field); } - static String canonicalStaticFieldSig(Field field) { + static String canonicalStaticFieldSig(@Nonnull Field field) { return "staticField "+canonicalFieldString(field); } diff --git a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/GenericWhitelist.java b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/GenericWhitelist.java index 7c87c490..3d1277ef 100644 --- a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/GenericWhitelist.java +++ b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/GenericWhitelist.java @@ -26,6 +26,7 @@ import java.io.IOException; + /** * Includes entries useful for general kinds of scripts. */ diff --git a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/ProxyWhitelist.java b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/ProxyWhitelist.java index 30a7fbbf..fa3f3a4e 100644 --- a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/ProxyWhitelist.java +++ b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/ProxyWhitelist.java @@ -24,6 +24,7 @@ package cd.go.contrib.plugins.configrepo.groovy.sandbox.whitelists; +import javax.annotation.concurrent.GuardedBy; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -34,23 +35,29 @@ * Aggregates several whitelists. */ public class ProxyWhitelist extends Whitelist { - + @GuardedBy("lock") private Collection originalDelegates; + @GuardedBy("lock") final List delegates = new ArrayList(); + @GuardedBy("lock") private final List methodSignatures = new ArrayList(); + @GuardedBy("lock") private final List newSignatures = new ArrayList(); + @GuardedBy("lock") private final List staticMethodSignatures = new ArrayList(); + @GuardedBy("lock") private final List fieldSignatures = new ArrayList(); + @GuardedBy("lock") private final List staticFieldSignatures = new ArrayList(); /** anything wrapping us, so that we can propagate {@link #reset} calls up the chain */ - private final Map wrappers = new WeakHashMap(); + private final Map wrappers = new WeakHashMap(); // TODO Consider StampedLock when we switch to Java8 for better performance - https://dzone.com/articles/a-look-at-stampedlock private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); @@ -76,28 +83,19 @@ public final void reset(Collection delegates) { fieldSignatures.clear(); this.delegates.add(new EnumeratingWhitelist() { - @Override - protected List methodSignatures() { + @Override protected List methodSignatures() { return methodSignatures; } - - @Override - protected List newSignatures() { + @Override protected List newSignatures() { return newSignatures; } - - @Override - protected List staticMethodSignatures() { + @Override protected List staticMethodSignatures() { return staticMethodSignatures; } - - @Override - protected List fieldSignatures() { + @Override protected List fieldSignatures() { return fieldSignatures; } - - @Override - protected List staticFieldSignatures() { + @Override protected List staticFieldSignatures() { return staticFieldSignatures; } }); @@ -126,6 +124,7 @@ protected List staticFieldSignatures() { fieldSignatures.addAll(pw.fieldSignatures); staticFieldSignatures.addAll(pw.staticFieldSignatures); } else { + Objects.requireNonNull(delegate); this.delegates.add(delegate); } } @@ -134,7 +133,7 @@ protected List staticFieldSignatures() { } if (this.wrappers.isEmpty()) { // Top-level ProxyWhitelist should be the only cache // Top-level ProxyWhitelist should precache the statically permitted signatures - ((EnumeratingWhitelist) (this.delegates.get(0))).precache(); + ((EnumeratingWhitelist)(this.delegates.get(0))).precache(); } } finally { writer.unlock(); @@ -145,8 +144,7 @@ public ProxyWhitelist(Whitelist... delegates) { this(Arrays.asList(delegates)); } - @Override - public final boolean permitsMethod(Method method, Object receiver, Object[] args) { + @Override public final boolean permitsMethod(Method method, Object receiver, Object[] args) { lock.readLock().lock(); try { for (Whitelist delegate : delegates) { @@ -160,8 +158,7 @@ public final boolean permitsMethod(Method method, Object receiver, Object[] args return false; } - @Override - public final boolean permitsConstructor(Constructor constructor, Object[] args) { + @Override public final boolean permitsConstructor(Constructor constructor, Object[] args) { lock.readLock().lock(); try { for (Whitelist delegate : delegates) { @@ -175,8 +172,7 @@ public final boolean permitsConstructor(Constructor constructor, Object[] arg return false; } - @Override - public final boolean permitsStaticMethod(Method method, Object[] args) { + @Override public final boolean permitsStaticMethod(Method method, Object[] args) { lock.readLock().lock(); try { for (Whitelist delegate : delegates) { @@ -190,8 +186,7 @@ public final boolean permitsStaticMethod(Method method, Object[] args) { return false; } - @Override - public final boolean permitsFieldGet(Field field, Object receiver) { + @Override public final boolean permitsFieldGet(Field field, Object receiver) { lock.readLock().lock(); try { for (Whitelist delegate : delegates) { @@ -205,8 +200,7 @@ public final boolean permitsFieldGet(Field field, Object receiver) { return false; } - @Override - public final boolean permitsFieldSet(Field field, Object receiver, Object value) { + @Override public final boolean permitsFieldSet(Field field, Object receiver, Object value) { lock.readLock().lock(); try { for (Whitelist delegate : delegates) { @@ -221,8 +215,7 @@ public final boolean permitsFieldSet(Field field, Object receiver, Object value) return false; } - @Override - public final boolean permitsStaticFieldGet(Field field) { + @Override public final boolean permitsStaticFieldGet(Field field) { lock.readLock().lock(); try { for (Whitelist delegate : delegates) { @@ -236,8 +229,7 @@ public final boolean permitsStaticFieldGet(Field field) { return false; } - @Override - public final boolean permitsStaticFieldSet(Field field, Object value) { + @Override public final boolean permitsStaticFieldSet(Field field, Object value) { lock.readLock().lock(); try { for (Whitelist delegate : delegates) { diff --git a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/StaticWhitelist.java b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/StaticWhitelist.java index 4770b41c..fd3e8f71 100644 --- a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/StaticWhitelist.java +++ b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/StaticWhitelist.java @@ -26,6 +26,8 @@ import cd.go.contrib.plugins.configrepo.groovy.sandbox.RejectedAccessException; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -40,6 +42,16 @@ * Whitelist based on a static file. */ public final class StaticWhitelist extends EnumeratingWhitelist { + private static final String[] PERMANENTLY_BLACKLISTED_METHODS = { + "method java.lang.Runtime exit int", + "method java.lang.Runtime halt int", + }; + + private static final String[] PERMANENTLY_BLACKLISTED_STATIC_METHODS = { + "staticMethod java.lang.System exit int", + }; + + private static final String[] PERMANENTLY_BLACKLISTED_CONSTRUCTORS = new String[0]; final List methodSignatures = new ArrayList(); final List newSignatures = new ArrayList(); @@ -73,7 +85,8 @@ public StaticWhitelist(String... lines) throws IOException { * @param line Line to filter. * @return {@code null} if the like must be skipped or the content to process if not. */ - static String filter(String line) { + static @CheckForNull + String filter(@Nonnull String line) { line = line.trim(); if (line.isEmpty() || line.startsWith("#")) { return null; @@ -81,7 +94,58 @@ static String filter(String line) { return line; } - static Signature parse(String line) throws IOException { + /** + * Returns true if the given method is permanently blacklisted in {@link #PERMANENTLY_BLACKLISTED_METHODS} + */ + public static boolean isPermanentlyBlacklistedMethod(@Nonnull Method m) { + String signature = canonicalMethodSig(m); + + for (String s : PERMANENTLY_BLACKLISTED_METHODS) { + if (s.equals(signature)) { + return true; + } + } + + return false; + } + + /** + * Returns true if the given method is permanently blacklisted in {@link #PERMANENTLY_BLACKLISTED_STATIC_METHODS} + */ + public static boolean isPermanentlyBlacklistedStaticMethod(@Nonnull Method m) { + String signature = canonicalStaticMethodSig(m); + + for (String s : PERMANENTLY_BLACKLISTED_STATIC_METHODS) { + if (s.equals(signature)) { + return true; + } + } + + return false; + } + + /** + * Returns true if the given constructor is permanently blacklisted in {@link #PERMANENTLY_BLACKLISTED_CONSTRUCTORS} + */ + public static boolean isPermanentlyBlacklistedConstructor(@Nonnull Constructor c) { + String signature = canonicalConstructorSig(c); + + for (String s : PERMANENTLY_BLACKLISTED_CONSTRUCTORS) { + if (s.equals(signature)) { + return true; + } + } + + return false; + } + + /** + * Parse a signature line into a {@link Signature}. + * @param line The signature string + * @return the equivalent {@link Signature} + * @throws IOException if the signature string could not be parsed. + */ + public static Signature parse(String line) throws IOException { String[] toks = line.split(" "); if (toks[0].equals("method")) { if (toks.length < 3) { @@ -113,6 +177,31 @@ static Signature parse(String line) throws IOException { } } + /** + * Checks if the signature is permanently blacklisted, and so shouldn't show up in the pending approval list. + * @param signature the signature to check + * @return true if the signature is permanently blacklisted, false otherwise. + */ + public static boolean isPermanentlyBlacklisted(String signature) { + for (String s : PERMANENTLY_BLACKLISTED_METHODS) { + if (s.equals(signature)) { + return true; + } + } + for (String s : PERMANENTLY_BLACKLISTED_STATIC_METHODS) { + if (s.equals(signature)) { + return true; + } + } + for (String s : PERMANENTLY_BLACKLISTED_CONSTRUCTORS) { + if (s.equals(signature)) { + return true; + } + } + + return false; + } + private void add(String line) throws IOException { Signature s = parse(line); if (s instanceof StaticMethodSignature) { @@ -157,31 +246,31 @@ public static StaticWhitelist from(URL definition) throws IOException { return staticFieldSignatures; } - public static RejectedAccessException rejectMethod(Method m) { + public static RejectedAccessException rejectMethod(@Nonnull Method m) { assert (m.getModifiers() & Modifier.STATIC) == 0; return blacklist(new RejectedAccessException("method", EnumeratingWhitelist.getName(m.getDeclaringClass()) + " " + m.getName() + printArgumentTypes(m.getParameterTypes()))); } - public static RejectedAccessException rejectMethod(Method m, String info) { + public static RejectedAccessException rejectMethod(@Nonnull Method m, String info) { assert (m.getModifiers() & Modifier.STATIC) == 0; return blacklist(new RejectedAccessException("method", EnumeratingWhitelist.getName(m.getDeclaringClass()) + " " + m.getName() + printArgumentTypes(m.getParameterTypes()), info)); } - public static RejectedAccessException rejectNew(Constructor c) { + public static RejectedAccessException rejectNew(@Nonnull Constructor c) { return blacklist(new RejectedAccessException("new", EnumeratingWhitelist.getName(c.getDeclaringClass()) + printArgumentTypes(c.getParameterTypes()))); } - public static RejectedAccessException rejectStaticMethod(Method m) { + public static RejectedAccessException rejectStaticMethod(@Nonnull Method m) { assert (m.getModifiers() & Modifier.STATIC) != 0; return blacklist(new RejectedAccessException("staticMethod", EnumeratingWhitelist.getName(m.getDeclaringClass()) + " " + m.getName() + printArgumentTypes(m.getParameterTypes()))); } - public static RejectedAccessException rejectField(Field f) { + public static RejectedAccessException rejectField(@Nonnull Field f) { assert (f.getModifiers() & Modifier.STATIC) == 0; return blacklist(new RejectedAccessException("field", EnumeratingWhitelist.getName(f.getDeclaringClass()) + " " + f.getName())); } - public static RejectedAccessException rejectStaticField(Field f) { + public static RejectedAccessException rejectStaticField(@Nonnull Field f) { assert (f.getModifiers() & Modifier.STATIC) != 0; return blacklist(new RejectedAccessException("staticField", EnumeratingWhitelist.getName(f.getDeclaringClass()) + " " + f.getName())); } @@ -197,6 +286,7 @@ private static String printArgumentTypes(Class[] parameterTypes) { private static final Set BLACKLIST; +// @SuppressFBWarnings(value = "OS_OPEN_STREAM", justification = "https://sourceforge.net/p/findbugs/bugs/786/") private static Set loadBlacklist() throws IOException { InputStream is = StaticWhitelist.class.getResourceAsStream("blacklist"); try { @@ -229,7 +319,13 @@ private static RejectedAccessException blacklist(RejectedAccessException x) { if (BLACKLIST.contains(x.getSignature())) { x.setDangerous(true); } +// ScriptApproval.maybeRegister(x); return x; } +// @Restricted(NoExternalUse.class) // ScriptApproval + public static boolean isBlacklisted(String signature) { + return BLACKLIST.contains(signature); + } + } diff --git a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/Whitelist.java b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/Whitelist.java index 86841bb9..95f2601e 100644 --- a/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/Whitelist.java +++ b/sandbox/src/main/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/Whitelist.java @@ -25,6 +25,8 @@ package cd.go.contrib.plugins.configrepo.groovy.sandbox.whitelists; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -34,18 +36,28 @@ */ public abstract class Whitelist { - public abstract boolean permitsMethod(Method method, Object receiver, Object[] args); + /** + * Checks whether a given virtual method may be invoked. + *

Note that {@code method} should not be implementing or overriding a method in a supertype; + * in such a case the caller must pass that supertype method instead. + * In other words, call site selection is the responsibility of the caller (such as {@link cd.go.contrib.plugins.configrepo.groovy.sandbox.GroovySandbox}), not the whitelist. + * @param method a method defined in the JVM + * @param receiver {@code this}, the receiver of the method call + * @param args zero or more arguments + * @return true to allow the method to be called, false to reject it + */ + public abstract boolean permitsMethod(@Nonnull Method method, @Nonnull Object receiver, @Nonnull Object[] args); - public abstract boolean permitsConstructor(Constructor constructor, Object[] args); + public abstract boolean permitsConstructor(@Nonnull Constructor constructor, @Nonnull Object[] args); - public abstract boolean permitsStaticMethod(Method method, Object[] args); + public abstract boolean permitsStaticMethod(@Nonnull Method method, @Nonnull Object[] args); - public abstract boolean permitsFieldGet(Field field, Object receiver); + public abstract boolean permitsFieldGet(@Nonnull Field field, @Nonnull Object receiver); - public abstract boolean permitsFieldSet(Field field, Object receiver, Object value); + public abstract boolean permitsFieldSet(@Nonnull Field field, @Nonnull Object receiver, @CheckForNull Object value); - public abstract boolean permitsStaticFieldGet(Field field); + public abstract boolean permitsStaticFieldGet(@Nonnull Field field); - public abstract boolean permitsStaticFieldSet(Field field, Object value); + public abstract boolean permitsStaticFieldSet(@Nonnull Field field, @CheckForNull Object value); } diff --git a/sandbox/src/main/resources/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/blacklist b/sandbox/src/main/resources/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/blacklist index df997d28..8839d79b 100644 --- a/sandbox/src/main/resources/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/blacklist +++ b/sandbox/src/main/resources/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/blacklist @@ -9,7 +9,17 @@ method groovy.lang.GroovyObject invokeMethod java.lang.String java.lang.Object method groovy.lang.GroovyObject setMetaClass groovy.lang.MetaClass method groovy.lang.GroovyObject setProperty java.lang.String java.lang.Object -# Raw file operations could be used to compromise the server. +# Variant of Jenkins.getInstance, see below. +# staticMethod hudson.model.Hudson getInstance + +# Up to no good… +# staticMethod hudson.model.User current +# staticMethod hudson.model.User get java.lang.String +# staticMethod hudson.model.User get java.lang.String boolean +# staticMethod hudson.model.User get java.lang.String boolean java.util.Map +# staticMethod hudson.model.User getAll + +# Raw file operations could be used to compromise the Jenkins master. staticMethod java.io.File createTempFile java.lang.String java.lang.String staticMethod java.io.File createTempFile java.lang.String java.lang.String java.io.File new java.io.File java.lang.String @@ -74,6 +84,9 @@ method java.net.URL openStream staticMethod java.nio.file.Paths get java.lang.String java.lang.String[] staticMethod java.nio.file.Paths get java.net.URI +# Do not even get started… +#staticMethod jenkins.model.Jenkins getInstance + # More process execution, Groovy-style: staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.lang.String java.lang.String[] java.io.File @@ -90,3 +103,10 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.Ob staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getMetaPropertyValues java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getProperties java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods putAt java.lang.Object java.lang.String java.lang.Object + +# SECURITY-1353 +staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter asType java.lang.Object java.lang.Class +staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter castToType java.lang.Object java.lang.Class + +# TODO do we need a @Blacklisted annotation? +# method org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper getRawBuild diff --git a/sandbox/src/main/resources/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/generic-whitelist b/sandbox/src/main/resources/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/generic-whitelist index 817bdf33..b4997222 100644 --- a/sandbox/src/main/resources/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/generic-whitelist +++ b/sandbox/src/main/resources/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/generic-whitelist @@ -1,3 +1,6 @@ +# groovy-cps - not needed from workflow-cps 2.33+ +# staticMethod com.cloudbees.groovy.cps.CpsDefaultGroovyMethods each java.util.Iterator groovy.lang.Closure + # Groovy: staticMethod groovy.json.JsonOutput prettyPrint java.lang.String staticMethod groovy.json.JsonOutput toJson java.lang.Boolean @@ -29,15 +32,40 @@ method groovy.lang.Closure ncurry int java.lang.Object[] method groovy.lang.Closure setDelegate java.lang.Object method groovy.lang.Closure setResolveStrategy int method groovy.lang.GString plus java.lang.String +method groovy.lang.Range getFrom +method groovy.lang.Range getTo +method groovy.lang.Range step int +method groovy.lang.Range step int groovy.lang.Closure method groovy.lang.Script getBinding +method java.io.Flushable flush +# Useful to show stacktrace to the user. +new java.io.PrintWriter java.io.Writer # Needed for Groovy Templates to emit output: method java.io.PrintWriter print java.lang.Object method java.io.PrintWriter print java.lang.String +method java.io.Reader read +method java.io.Reader read char[] +method java.io.Reader read char[] int int +method java.io.Reader reset +method java.io.Reader skip long + # Useful general stuff: new java.io.StringReader java.lang.String new java.io.StringWriter + +method java.io.Writer write char[] +method java.io.Writer write char[] int int +method java.io.Writer write int +method java.io.Writer write java.lang.String +method java.io.Writer write java.lang.String int int + +method java.lang.Appendable append char +method java.lang.Appendable append java.lang.CharSequence +method java.lang.Appendable append java.lang.CharSequence int int +method java.lang.AutoCloseable close +method java.lang.Boolean booleanValue new java.lang.Boolean java.lang.String staticMethod java.lang.Boolean parseBoolean java.lang.String staticMethod java.lang.Boolean valueOf boolean @@ -46,10 +74,12 @@ method java.lang.CharSequence charAt int method java.lang.CharSequence length method java.lang.Class getName method java.lang.Class getSimpleName +method java.lang.Class isInstance java.lang.Object method java.lang.Comparable compareTo java.lang.Object new java.lang.Enum java.lang.String int method java.lang.Enum name method java.lang.Enum ordinal +new java.lang.Exception java.lang.String staticField java.lang.Integer MAX_VALUE # could add valueOf, though currently the staticField’s need to be whitelisted, which is the more likely use case staticMethod java.lang.Integer parseInt java.lang.String @@ -73,15 +103,22 @@ method java.lang.Number shortValue method java.lang.Object clone method java.lang.Object equals java.lang.Object method java.lang.Object getClass +method java.lang.Object hashCode method java.lang.Object toString method java.lang.String contains java.lang.CharSequence method java.lang.String endsWith java.lang.String method java.lang.String equalsIgnoreCase java.lang.String staticMethod java.lang.String format java.lang.String java.lang.Object[] staticMethod java.lang.String format java.util.Locale java.lang.String java.lang.Object[] +method java.lang.String indexOf int +method java.lang.String indexOf int int method java.lang.String indexOf java.lang.String +method java.lang.String indexOf java.lang.String int method java.lang.String isEmpty +method java.lang.String lastIndexOf int +method java.lang.String lastIndexOf int int method java.lang.String lastIndexOf java.lang.String +method java.lang.String lastIndexOf java.lang.String int method java.lang.String matches java.lang.String method java.lang.String replace char char method java.lang.String replace java.lang.CharSequence java.lang.CharSequence @@ -99,53 +136,309 @@ method java.lang.String toUpperCase java.util.Locale method java.lang.String trim staticMethod java.lang.String valueOf java.lang.Object staticMethod java.lang.System currentTimeMillis +method java.lang.Throwable getCause method java.lang.Throwable getMessage +method java.math.BigDecimal multiply java.math.BigDecimal new java.net.MalformedURLException java.lang.String new java.net.URL java.lang.String staticMethod java.net.URLDecoder decode java.lang.String java.lang.String staticMethod java.net.URLEncoder encode java.lang.String java.lang.String +staticField java.text.DateFormat AM_PM_FIELD +staticField java.text.DateFormat DATE_FIELD +staticField java.text.DateFormat DAY_OF_WEEK_FIELD +staticField java.text.DateFormat DAY_OF_WEEK_IN_MONTH_FIELD +staticField java.text.DateFormat DAY_OF_YEAR_FIELD +staticField java.text.DateFormat DEFAULT +staticField java.text.DateFormat ERA_FIELD +staticField java.text.DateFormat FULL +staticField java.text.DateFormat HOUR0_FIELD +staticField java.text.DateFormat HOUR1_FIELD +staticField java.text.DateFormat HOUR_OF_DAY0_FIELD +staticField java.text.DateFormat HOUR_OF_DAY1_FIELD +staticField java.text.DateFormat LONG +staticField java.text.DateFormat MEDIUM +staticField java.text.DateFormat MILLISECOND_FIELD +staticField java.text.DateFormat MINUTE_FIELD +staticField java.text.DateFormat MONTH_FIELD +staticField java.text.DateFormat SECOND_FIELD +staticField java.text.DateFormat SHORT +staticField java.text.DateFormat TIMEZONE_FIELD +staticField java.text.DateFormat WEEK_OF_MONTH_FIELD +staticField java.text.DateFormat WEEK_OF_YEAR_FIELD +staticField java.text.DateFormat YEAR_FIELD +method java.text.DateFormat format java.util.Date +method java.text.DateFormat format java.util.Date java.lang.StringBuffer java.text.FieldPosition +staticMethod java.text.DateFormat getAvailableLocales +method java.text.DateFormat getCalendar +staticMethod java.text.DateFormat getDateInstance +staticMethod java.text.DateFormat getDateInstance int +staticMethod java.text.DateFormat getDateInstance int java.util.Locale +staticMethod java.text.DateFormat getDateTimeInstance +staticMethod java.text.DateFormat getDateTimeInstance int int +staticMethod java.text.DateFormat getDateTimeInstance int int java.util.Locale +staticMethod java.text.DateFormat getInstance +method java.text.DateFormat getNumberFormat +staticMethod java.text.DateFormat getTimeInstance +staticMethod java.text.DateFormat getTimeInstance int +staticMethod java.text.DateFormat getTimeInstance int java.util.Locale +method java.text.DateFormat getTimeZone +method java.text.DateFormat isLenient +method java.text.DateFormat parse java.lang.String +method java.text.DateFormat parse java.lang.String java.text.ParsePosition +method java.text.DateFormat setCalendar java.util.Calendar +method java.text.DateFormat setLenient boolean +method java.text.DateFormat setNumberFormat java.text.NumberFormat +method java.text.DateFormat setTimeZone java.util.TimeZone method java.text.Format format java.lang.Object new java.text.SimpleDateFormat java.lang.String +method java.time.LocalDate getDayOfMonth +method java.time.LocalDate getDayOfWeek +method java.time.LocalDate getDayOfYear +method java.time.LocalDate getMonth +method java.time.LocalDate getMonthValue +method java.time.LocalDate getYear +method java.time.LocalDate minusDays long +method java.time.LocalDate minusMonths long +method java.time.LocalDate minusWeeks long +method java.time.LocalDate minusYears long +staticMethod java.time.LocalDate now +staticMethod java.time.LocalDate parse java.lang.CharSequence +staticMethod java.time.LocalDate parse java.lang.CharSequence java.time.format.DateTimeFormatter +method java.time.LocalDate plusDays long +method java.time.LocalDate plusMonths long +method java.time.LocalDate plusWeeks long +method java.time.LocalDate plusYears long +method java.time.LocalDate withDayOfMonth int +method java.time.LocalDate withDayOfYear int +method java.time.LocalDate withMonth int +method java.time.LocalDate withYear int +method java.time.LocalDateTime getDayOfMonth +method java.time.LocalDateTime getDayOfWeek +method java.time.LocalDateTime getDayOfYear +method java.time.LocalDateTime getHour +method java.time.LocalDateTime getMinute +method java.time.LocalDateTime getMonth +method java.time.LocalDateTime getMonthValue +method java.time.LocalDateTime getNano +method java.time.LocalDateTime getSecond +method java.time.LocalDateTime getYear +method java.time.LocalDateTime minusDays long +method java.time.LocalDateTime minusHours long +method java.time.LocalDateTime minusMinutes long +method java.time.LocalDateTime minusMonths long +method java.time.LocalDateTime minusNanos long +method java.time.LocalDateTime minusSeconds long +method java.time.LocalDateTime minusWeeks long +method java.time.LocalDateTime minusYears long +staticMethod java.time.LocalDateTime now +staticMethod java.time.LocalDateTime of java.time.LocalDate java.time.LocalTime +staticMethod java.time.LocalDateTime parse java.lang.CharSequence +staticMethod java.time.LocalDateTime parse java.lang.CharSequence java.time.format.DateTimeFormatter +method java.time.LocalDateTime plusDays long +method java.time.LocalDateTime plusHours long +method java.time.LocalDateTime plusMinutes long +method java.time.LocalDateTime plusMonths long +method java.time.LocalDateTime plusNanos long +method java.time.LocalDateTime plusSeconds long +method java.time.LocalDateTime plusWeeks long +method java.time.LocalDateTime plusYears long +method java.time.LocalDateTime withDayOfMonth int +method java.time.LocalDateTime withDayOfYear int +method java.time.LocalDateTime withHour int +method java.time.LocalDateTime withMinute int +method java.time.LocalDateTime withMonth int +method java.time.LocalDateTime withNano int +method java.time.LocalDateTime withSecond int +method java.time.LocalDateTime withYear int +staticField java.time.LocalTime MIDNIGHT +staticField java.time.LocalTime NOON +method java.time.LocalTime format java.time.format.DateTimeFormatter +method java.time.LocalTime getHour +method java.time.LocalTime getMinute +method java.time.LocalTime getNano +method java.time.LocalTime getSecond +method java.time.LocalTime minusHours long +method java.time.LocalTime minusMinutes long +method java.time.LocalTime minusNanos long +method java.time.LocalTime minusSeconds long +staticMethod java.time.LocalTime now +staticMethod java.time.LocalTime parse java.lang.CharSequence +staticMethod java.time.LocalTime parse java.lang.CharSequence java.time.format.DateTimeFormatter +method java.time.LocalTime plusHours long +method java.time.LocalTime plusMinutes long +method java.time.LocalTime plusNanos long +method java.time.LocalTime plusSeconds long +method java.time.LocalTime toNanoOfDay +method java.time.LocalTime toSecondOfDay +method java.time.LocalTime withHour int +method java.time.LocalTime withMinute int +method java.time.LocalTime withNano int +method java.time.LocalTime withSecond int +method java.time.chrono.ChronoLocalDate format java.time.format.DateTimeFormatter +method java.time.chrono.ChronoLocalDate getEra +method java.time.chrono.ChronoLocalDate isLeapYear +method java.time.chrono.ChronoLocalDate lengthOfMonth +method java.time.chrono.ChronoLocalDate lengthOfYear +method java.time.chrono.ChronoLocalDate toEpochDay +method java.time.chrono.ChronoLocalDateTime format java.time.format.DateTimeFormatter +staticField java.time.format.DateTimeFormatter BASIC_ISO_DATE +staticField java.time.format.DateTimeFormatter ISO_DATE +staticField java.time.format.DateTimeFormatter ISO_DATE_TIME +staticField java.time.format.DateTimeFormatter ISO_INSTANT +staticField java.time.format.DateTimeFormatter ISO_LOCAL_DATE +staticField java.time.format.DateTimeFormatter ISO_LOCAL_DATE_TIME +staticField java.time.format.DateTimeFormatter ISO_LOCAL_TIME +staticField java.time.format.DateTimeFormatter ISO_OFFSET_DATE +staticField java.time.format.DateTimeFormatter ISO_OFFSET_DATE_TIME +staticField java.time.format.DateTimeFormatter ISO_OFFSET_TIME +staticField java.time.format.DateTimeFormatter ISO_ORDINAL_DATE +staticField java.time.format.DateTimeFormatter ISO_TIME +staticField java.time.format.DateTimeFormatter ISO_WEEK_DATE +staticField java.time.format.DateTimeFormatter ISO_ZONED_DATE_TIME +staticField java.time.format.DateTimeFormatter RFC_1123_DATE_TIME +staticMethod java.time.format.DateTimeFormatter ofPattern java.lang.String new java.util.ArrayList java.util.Collection +staticMethod java.util.Arrays asList java.lang.Object[] staticMethod java.util.Arrays toString java.lang.Object[] +staticField java.util.Calendar ALL_STYLES +staticField java.util.Calendar AM staticField java.util.Calendar AM_PM +staticField java.util.Calendar APRIL +staticField java.util.Calendar AUGUST staticField java.util.Calendar DATE staticField java.util.Calendar DAY_OF_MONTH staticField java.util.Calendar DAY_OF_WEEK staticField java.util.Calendar DAY_OF_WEEK_IN_MONTH staticField java.util.Calendar DAY_OF_YEAR +staticField java.util.Calendar DECEMBER staticField java.util.Calendar DST_OFFSET staticField java.util.Calendar ERA +staticField java.util.Calendar FEBRUARY staticField java.util.Calendar FIELD_COUNT +staticField java.util.Calendar FRIDAY staticField java.util.Calendar HOUR staticField java.util.Calendar HOUR_OF_DAY +staticField java.util.Calendar JANUARY +staticField java.util.Calendar JULY +staticField java.util.Calendar JUNE +staticField java.util.Calendar LONG +staticField java.util.Calendar LONG_FORMAT +staticField java.util.Calendar LONG_STANDALONE +staticField java.util.Calendar MARCH +staticField java.util.Calendar MAY staticField java.util.Calendar MILLISECOND staticField java.util.Calendar MINUTE +staticField java.util.Calendar MONDAY staticField java.util.Calendar MONTH +staticField java.util.Calendar NARROW_FORMAT +staticField java.util.Calendar NARROW_STANDALONE +staticField java.util.Calendar NOVEMBER +staticField java.util.Calendar OCTOBER +staticField java.util.Calendar PM +staticField java.util.Calendar SATURDAY staticField java.util.Calendar SECOND +staticField java.util.Calendar SEPTEMBER +staticField java.util.Calendar SHORT +staticField java.util.Calendar SHORT_FORMAT +staticField java.util.Calendar SHORT_STANDALONE +staticField java.util.Calendar SUNDAY +staticField java.util.Calendar THURSDAY +staticField java.util.Calendar TUESDAY +staticField java.util.Calendar UNDECIMBER +staticField java.util.Calendar WEDNESDAY staticField java.util.Calendar WEEK_OF_MONTH staticField java.util.Calendar WEEK_OF_YEAR staticField java.util.Calendar YEAR staticField java.util.Calendar ZONE_OFFSET +method java.util.Calendar add int int +method java.util.Calendar after java.lang.Object +method java.util.Calendar before java.lang.Object +method java.util.Calendar clear +method java.util.Calendar clear int +method java.util.Calendar compareTo java.util.Calendar method java.util.Calendar get int +method java.util.Calendar getActualMaximum int +method java.util.Calendar getActualMinimum int +staticMethod java.util.Calendar getAvailableCalendarTypes +staticMethod java.util.Calendar getAvailableLocales +method java.util.Calendar getCalendarType +method java.util.Calendar getDisplayName int int java.util.Locale +method java.util.Calendar getDisplayNames int int java.util.Locale +method java.util.Calendar getFirstDayOfWeek +method java.util.Calendar getGreatestMinimum int staticMethod java.util.Calendar getInstance +staticMethod java.util.Calendar getInstance java.util.Locale +staticMethod java.util.Calendar getInstance java.util.TimeZone +staticMethod java.util.Calendar getInstance java.util.TimeZone java.util.Locale +method java.util.Calendar getLeastMaximum int +method java.util.Calendar getMaximum int +method java.util.Calendar getMinimalDaysInFirstWeek +method java.util.Calendar getMinimum int +method java.util.Calendar getTime +method java.util.Calendar getTimeInMillis +method java.util.Calendar getTimeZone +method java.util.Calendar getWeekYear +method java.util.Calendar getWeeksInWeekYear +method java.util.Calendar isLenient +method java.util.Calendar isSet int +method java.util.Calendar isWeekDateSupported +method java.util.Calendar roll int boolean +method java.util.Calendar roll int int +method java.util.Calendar set int int +method java.util.Calendar set int int int +method java.util.Calendar set int int int int int +method java.util.Calendar set int int int int int int +method java.util.Calendar setFirstDayOfWeek int +method java.util.Calendar setLenient boolean +method java.util.Calendar setMinimalDaysInFirstWeek int +method java.util.Calendar setTime java.util.Date +method java.util.Calendar setTimeInMillis long +method java.util.Calendar setTimeZone java.util.TimeZone +method java.util.Calendar setWeekDate int int int +method java.util.Calendar toInstant method java.util.Collection add java.lang.Object method java.util.Collection addAll java.util.Collection method java.util.Collection contains java.lang.Object +method java.util.Collection containsAll java.util.Collection method java.util.Collection isEmpty +method java.util.Collection remove java.lang.Object method java.util.Collection removeAll java.util.Collection method java.util.Collection retainAll java.util.Collection method java.util.Collection size -method java.util.Collection toArray -method java.util.Collection toArray Object[] staticMethod java.util.Collections sort java.util.List java.util.Comparator new java.util.Date +method java.util.Date after java.util.Date +method java.util.Date before java.util.Date +method java.util.Date getDate +method java.util.Date getDay +method java.util.Date getHours +method java.util.Date getMinutes +method java.util.Date getMonth +method java.util.Date getSeconds +method java.util.Date getTime +method java.util.Date getTimezoneOffset +method java.util.Date getYear new java.util.Date long +staticMethod java.util.Date parse java.lang.String +method java.util.Date setDate int +method java.util.Date setHours int +method java.util.Date setMinutes int +method java.util.Date setMonth int +method java.util.Date setSeconds int +method java.util.Date setTime long +method java.util.Date setYear int +method java.util.Date toGMTString +method java.util.Date toLocaleString new java.util.HashSet new java.util.HashSet java.util.Collection method java.util.Iterator hasNext method java.util.Iterator next +new java.util.LinkedHashSet +method java.util.List add int java.lang.Object method java.util.List get int +method java.util.List indexOf java.lang.Object +method java.util.List remove int +method java.util.List subList int int staticField java.util.Locale CANADA staticField java.util.Locale CANADA_FRENCH staticField java.util.Locale CHINESE @@ -167,16 +460,23 @@ staticField java.util.Locale UK staticField java.util.Locale US method java.util.Map clear method java.util.Map containsKey java.lang.Object +method java.util.Map containsValue java.lang.Object method java.util.Map entrySet method java.util.Map get java.lang.Object +method java.util.Map getOrDefault java.lang.Object java.lang.Object method java.util.Map isEmpty method java.util.Map keySet method java.util.Map put java.lang.Object java.lang.Object method java.util.Map putAll java.util.Map +method java.util.Map putIfAbsent java.lang.Object java.lang.Object +method java.util.Map remove java.lang.Object +method java.util.Map replace java.lang.Object java.lang.Object +method java.util.Map replace java.lang.Object java.lang.Object java.lang.Object method java.util.Map size method java.util.Map values method java.util.Map$Entry getKey method java.util.Map$Entry getValue +staticMethod java.util.Objects hash java.lang.Object[] staticMethod java.util.Objects requireNonNull java.lang.Object staticMethod java.util.Objects requireNonNull java.lang.Object java.lang.String new java.util.Random @@ -195,24 +495,92 @@ staticMethod java.util.UUID randomUUID method java.util.concurrent.Callable call staticField java.util.concurrent.TimeUnit HOURS staticField java.util.concurrent.TimeUnit MINUTES +method java.util.regex.MatchResult end +method java.util.regex.MatchResult end int +method java.util.regex.MatchResult group method java.util.regex.MatchResult group int +method java.util.regex.MatchResult groupCount +method java.util.regex.MatchResult start +method java.util.regex.MatchResult start int +method java.util.regex.Matcher appendReplacement java.lang.StringBuffer java.lang.String +method java.util.regex.Matcher appendTail java.lang.StringBuffer +method java.util.regex.Matcher find +method java.util.regex.Matcher group java.lang.String +method java.util.regex.Matcher hasAnchoringBounds +method java.util.regex.Matcher hasTransparentBounds +method java.util.regex.Matcher hitEnd +method java.util.regex.Matcher lookingAt method java.util.regex.Matcher matches +method java.util.regex.Matcher pattern +staticMethod java.util.regex.Matcher quoteReplacement java.lang.String +method java.util.regex.Matcher region int int +method java.util.regex.Matcher regionEnd +method java.util.regex.Matcher regionStart method java.util.regex.Matcher replaceAll java.lang.String method java.util.regex.Matcher replaceFirst java.lang.String +method java.util.regex.Matcher requireEnd +method java.util.regex.Matcher reset +method java.util.regex.Matcher reset java.lang.CharSequence +method java.util.regex.Matcher toMatchResult +method java.util.regex.Matcher useAnchoringBounds boolean +method java.util.regex.Matcher usePattern java.util.regex.Pattern +method java.util.regex.Matcher useTransparentBounds boolean staticMethod java.util.regex.Pattern compile java.lang.String +staticMethod java.util.regex.Pattern compile java.lang.String int +method java.util.regex.Pattern flags method java.util.regex.Pattern matcher java.lang.CharSequence +staticMethod java.util.regex.Pattern matches java.lang.String java.lang.CharSequence +method java.util.regex.Pattern pattern +staticMethod java.util.regex.Pattern quote java.lang.String +method java.util.regex.Pattern split java.lang.CharSequence +method java.util.regex.Pattern split java.lang.CharSequence int method net.sf.json.JSON isArray method net.sf.json.JSON size # More Groovy: +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods downto java.util.Calendar java.util.Calendar groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods downto java.util.Date java.util.Date groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods format java.util.Calendar java.lang.String staticMethod org.codehaus.groovy.runtime.DateGroovyMethods format java.util.Date java.lang.String staticMethod org.codehaus.groovy.runtime.DateGroovyMethods format java.util.Date java.lang.String java.util.TimeZone +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods getAt java.util.Calendar int +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods getAt java.util.Date int +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods getDateString java.util.Date staticMethod org.codehaus.groovy.runtime.DateGroovyMethods getDateTimeString java.util.Date +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods getTimeString java.util.Date +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods minus java.util.Calendar java.util.Calendar +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods minus java.util.Date int +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods minus java.util.Date java.util.Date +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods next java.util.Calendar +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods next java.util.Date +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods plus java.util.Date int +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods previous java.util.Calendar +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods previous java.util.Date +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods putAt java.util.Calendar int int +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods putAt java.util.Date int int +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods set java.util.Calendar java.util.Map +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods set java.util.Date java.util.Map +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods toCalendar java.util.Date +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods upto java.util.Calendar java.util.Calendar groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DateGroovyMethods upto java.util.Date java.util.Date groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods abs java.lang.Number +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods addAll java.util.Collection java.lang.Object[] staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods any java.lang.Iterable groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods any java.lang.Object groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods any java.util.Map groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods asBoolean java.lang.CharSequence staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods asBoolean java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods asImmutable java.util.Map staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods capitalize java.lang.String +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collate java.lang.Iterable int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collate java.lang.Iterable int boolean +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collate java.lang.Iterable int int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collate java.lang.Iterable int int boolean +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collate java.util.List int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collate java.util.List int boolean +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collate java.util.List int int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collate java.util.List int int boolean staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collect java.lang.Object groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collect java.util.Collection staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collect java.util.Collection groovy.lang.Closure @@ -239,7 +607,6 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collectNested java staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collectNested java.util.Collection groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods collectNested java.util.Collection java.util.Collection groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods combinations java.lang.Iterable groovy.lang.Closure -staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods contains Object[] java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods contains boolean[] java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods contains byte[] java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods contains char[] java.lang.Object @@ -247,12 +614,14 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods contains double[] staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods contains float[] java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods contains int[] java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods contains java.lang.Iterable java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods contains java.lang.Object[] java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods contains long[] java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods contains short[] java.lang.Object -staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods containsAll Collection[] java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods containsAll java.lang.Iterable java.lang.Object[] staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods count java.lang.Iterable groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods count java.lang.Object[] groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods count java.lang.Object[] java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods count java.lang.String java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods count java.util.Collection groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods count java.util.Collection java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods count java.util.Iterator groovy.lang.Closure @@ -263,6 +632,10 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods countBy java.util. staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods countBy java.util.Iterator groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods countBy java.util.Map groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods disjoint java.util.Collection java.util.Collection +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods drop java.lang.Iterable int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods drop java.util.List int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods dropRight java.lang.Iterable int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods dropRight java.util.List int staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods each java.lang.Iterable groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods each java.lang.Object groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods each java.util.Collection groovy.lang.Closure @@ -284,6 +657,7 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods eachWithIndex java staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods eachWithIndex java.util.Set groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods eachWithIndex java.util.SortedSet groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods every java.lang.Iterable groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods every java.lang.Object groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods every java.util.Iterator groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods every java.util.Map groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods find java.lang.Object[] groovy.lang.Closure @@ -310,12 +684,20 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods first java.lang.It staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods first java.lang.Object[] staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods first java.util.List staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods flatten java.util.Collection groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods flatten java.util.List staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods get java.util.Map java.lang.Object java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.Iterable int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.Object[] groovy.lang.IntRange +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.Object[] groovy.lang.Range +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.Object[] java.util.Collection staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.String groovy.lang.IntRange staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.String groovy.lang.Range staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.String int staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.String java.util.Collection +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.util.List groovy.lang.Range staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.util.List int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.util.List java.lang.Number +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.util.List java.util.Collection staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.util.Map java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.util.regex.Matcher int staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getChars java.lang.String @@ -338,6 +720,7 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods inject java.util.I staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods inject java.util.Map java.lang.Object groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods intersect java.util.Set java.lang.Iterable staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods isCase java.util.Collection java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods isInteger java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods isNumber java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods iterator java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods join boolean[] java.lang.String @@ -355,7 +738,9 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods join short[] java. staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods last java.lang.Iterable staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods last java.lang.Object[] staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods last java.util.List +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods leftShift java.io.Writer java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods leftShift java.lang.String java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods leftShift java.lang.StringBuffer java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods leftShift java.util.Collection java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods leftShift java.util.List java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods leftShift java.util.Map java.util.Map @@ -363,10 +748,34 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods leftShift java.uti staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods max java.lang.Iterable groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods max java.lang.Object[] groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods max java.util.Collection groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods max java.util.Iterator groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods max java.util.Map groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods min java.lang.Iterable groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods min java.lang.Object[] groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods min java.util.Collection groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods min java.util.Iterator groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods min java.util.Map groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.lang.Character java.lang.Character +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.lang.Character java.lang.Number +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.lang.Iterable java.lang.Iterable +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.lang.Iterable java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.lang.Number java.lang.Character +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.lang.Object[] java.lang.Iterable +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.lang.Object[] java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.lang.Object[] java.lang.Object[] +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.lang.String java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.util.Collection java.util.Collection +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.util.List java.lang.Iterable staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.util.List java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.util.List java.util.Collection +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.util.Map java.util.Map +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.util.Set java.lang.Iterable +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.util.Set java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.util.Set java.util.Collection +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.util.SortedSet java.lang.Iterable +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.util.SortedSet java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods minus java.util.SortedSet java.util.Collection +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods multiply java.lang.String java.lang.Number staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods next java.lang.Number staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods or java.lang.Boolean java.lang.Boolean staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods padLeft java.lang.CharSequence java.lang.Number @@ -380,8 +789,11 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods padRight java.lang staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods permutations java.util.List staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods permutations java.util.List groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.lang.CharSequence java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.lang.Character java.lang.Character +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.lang.Character java.lang.Number staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.lang.Iterable java.lang.Iterable staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.lang.Iterable java.lang.Object +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.lang.Number java.lang.Character staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.lang.Number java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.lang.Object[] java.lang.Iterable staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.lang.Object[] java.lang.Object @@ -406,6 +818,7 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.util.Set staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.util.SortedSet java.lang.Iterable staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.util.SortedSet java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods plus java.util.SortedSet java.util.Collection +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods pop java.util.List staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods power java.lang.Integer java.lang.Integer staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods power java.lang.Long java.lang.Integer staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods power java.lang.Number java.lang.Number @@ -417,8 +830,10 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods putAll java.util.M staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods putAt java.util.List int java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods putAt java.util.Map java.lang.Object java.lang.Object staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods removeAll java.util.Collection groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods removeLast java.util.List staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods retainAll java.util.Collection groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods reverse java.util.Iterator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods reverse java.util.List staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods reverseEach java.lang.Object[] groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods reverseEach java.util.List groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods reverseEach java.util.Map groovy.lang.Closure @@ -433,6 +848,11 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods size java.lang.Str staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods size java.lang.StringBuffer staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods size long[] staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods size short[] +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.lang.Iterable +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.lang.Iterable boolean +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.lang.Iterable boolean groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.lang.Iterable boolean java.util.Comparator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.lang.Iterable groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.lang.Object[] staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.lang.Object[] boolean staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.lang.Object[] boolean groovy.lang.Closure @@ -442,6 +862,11 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.lang.Obj staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.util.Collection staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.util.Collection groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.util.Collection java.util.Comparator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.util.Iterator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.util.Iterator groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.util.Iterator java.util.Comparator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.util.Map +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sort java.util.Map groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods split java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods split java.util.Collection groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods split java.util.List groovy.lang.Closure @@ -452,6 +877,8 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods stripIndent java.l staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods stripIndent java.lang.String int staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods stripMargin java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods stripMargin java.lang.String java.lang.String +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods subMap java.util.Map java.lang.Object[] +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods subMap java.util.Map java.util.Collection staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sum java.lang.Object[] groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sum java.lang.Object[] java.lang.Object groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sum java.util.Collection @@ -459,6 +886,11 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sum java.util.Coll staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods sum java.util.Collection java.lang.Object groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods tail java.util.List staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods take java.lang.CharSequence int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods take java.lang.Iterable int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods take java.util.List int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods takeRight java.lang.Iterable int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods takeRight java.util.List int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods times java.lang.Number groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toBoolean java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toInteger java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toList boolean[] @@ -477,14 +909,44 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toList long[] staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toList short[] staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toListString java.util.Collection staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toListString java.util.Collection int +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toLong java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toMapString java.util.Map staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toMapString java.util.Map int staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSet java.util.Collection +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.lang.Iterable +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.lang.Iterable groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.lang.Iterable java.util.Comparator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.lang.Object[] +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.lang.Object[] groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.lang.Object[] java.util.Comparator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.util.Iterator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.util.Iterator groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.util.Iterator java.util.Comparator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.util.Map +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.util.Map groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.util.Map java.util.Comparator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.util.SortedMap +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toSorted java.util.SortedSet staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods tokenize java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods tokenize java.lang.String java.lang.Character staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods tokenize java.lang.String java.lang.String +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods transpose java.util.List staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.Collection +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.Collection boolean +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.Collection boolean groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.Collection boolean java.util.Comparator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.Collection groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.Collection java.util.Comparator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.Iterator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.Iterator groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.Iterator java.util.Comparator staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.List +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.List boolean +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.List boolean groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.List boolean java.util.Comparator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.List groovy.lang.Closure +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods unique java.util.List java.util.Comparator +staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods with java.lang.Object groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods withDefault java.util.List groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods withDefault java.util.Map groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods withLazyDefault java.util.List groovy.lang.Closure @@ -494,7 +956,6 @@ staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport createSimil staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport createSimilarMap java.util.Map staticMethod org.codehaus.groovy.runtime.InvokerHelper asIterator java.lang.Object staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter bitwiseNegate java.lang.Object -staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter castToType java.lang.Object java.lang.Class staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter compareEqual java.lang.Object java.lang.Object staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter compareGreaterThan java.lang.Object java.lang.Object staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter compareGreaterThanEqual java.lang.Object java.lang.Object @@ -508,18 +969,28 @@ staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter createRange java. staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter findRegex java.lang.Object java.lang.Object staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter isCase java.lang.Object java.lang.Object staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter matchRegex java.lang.Object java.lang.Object +staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter unaryMinus java.lang.Object +staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter unaryPlus java.lang.Object staticMethod org.codehaus.groovy.runtime.StringGroovyMethods capitalize java.lang.CharSequence +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods capitalize java.lang.String staticMethod org.codehaus.groovy.runtime.StringGroovyMethods center java.lang.CharSequence java.lang.Number staticMethod org.codehaus.groovy.runtime.StringGroovyMethods center java.lang.CharSequence java.lang.Number java.lang.CharSequence +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods center java.lang.String java.lang.Number +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods center java.lang.String java.lang.Number java.lang.String staticMethod org.codehaus.groovy.runtime.StringGroovyMethods count java.lang.CharSequence java.lang.CharSequence +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods count java.lang.String java.lang.String staticMethod org.codehaus.groovy.runtime.StringGroovyMethods denormalize java.lang.CharSequence +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods denormalize java.lang.String staticMethod org.codehaus.groovy.runtime.StringGroovyMethods drop java.lang.CharSequence int staticMethod org.codehaus.groovy.runtime.StringGroovyMethods dropWhile java.lang.CharSequence groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.StringGroovyMethods eachMatch java.lang.String java.lang.String groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.StringGroovyMethods eachMatch java.lang.String java.util.regex.Pattern groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.StringGroovyMethods expand java.lang.CharSequence staticMethod org.codehaus.groovy.runtime.StringGroovyMethods expand java.lang.CharSequence int +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods expand java.lang.String +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods expand java.lang.String int staticMethod org.codehaus.groovy.runtime.StringGroovyMethods expandLine java.lang.CharSequence int +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods expandLine java.lang.String int staticMethod org.codehaus.groovy.runtime.StringGroovyMethods find java.lang.CharSequence java.lang.CharSequence staticMethod org.codehaus.groovy.runtime.StringGroovyMethods find java.lang.CharSequence java.lang.CharSequence groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.StringGroovyMethods find java.lang.CharSequence java.util.regex.Pattern @@ -533,14 +1004,23 @@ staticMethod org.codehaus.groovy.runtime.StringGroovyMethods findAll java.lang.C staticMethod org.codehaus.groovy.runtime.StringGroovyMethods findAll java.lang.String java.lang.String groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.StringGroovyMethods findAll java.lang.String java.util.regex.Pattern groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.StringGroovyMethods isAllWhitespace java.lang.CharSequence +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods isAllWhitespace java.lang.String staticMethod org.codehaus.groovy.runtime.StringGroovyMethods isDouble java.lang.CharSequence +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods isDouble java.lang.String staticMethod org.codehaus.groovy.runtime.StringGroovyMethods isFloat java.lang.CharSequence +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods isFloat java.lang.String staticMethod org.codehaus.groovy.runtime.StringGroovyMethods isInteger java.lang.CharSequence +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods isInteger java.lang.String staticMethod org.codehaus.groovy.runtime.StringGroovyMethods isLong java.lang.CharSequence +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods isLong java.lang.String staticMethod org.codehaus.groovy.runtime.StringGroovyMethods minus java.lang.CharSequence java.lang.Object staticMethod org.codehaus.groovy.runtime.StringGroovyMethods minus java.lang.CharSequence java.util.regex.Pattern +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods minus java.lang.String java.lang.Object +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods minus java.lang.String java.util.regex.Pattern staticMethod org.codehaus.groovy.runtime.StringGroovyMethods multiply java.lang.CharSequence java.lang.Number +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods multiply java.lang.String java.lang.Number staticMethod org.codehaus.groovy.runtime.StringGroovyMethods normalize java.lang.CharSequence +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods normalize java.lang.String staticMethod org.codehaus.groovy.runtime.StringGroovyMethods padLeft java.lang.CharSequence java.lang.Number staticMethod org.codehaus.groovy.runtime.StringGroovyMethods padLeft java.lang.CharSequence java.lang.Number java.lang.CharSequence staticMethod org.codehaus.groovy.runtime.StringGroovyMethods padLeft java.lang.String java.lang.Number @@ -549,6 +1029,8 @@ staticMethod org.codehaus.groovy.runtime.StringGroovyMethods padRight java.lang. staticMethod org.codehaus.groovy.runtime.StringGroovyMethods padRight java.lang.CharSequence java.lang.Number java.lang.CharSequence staticMethod org.codehaus.groovy.runtime.StringGroovyMethods padRight java.lang.String java.lang.Number staticMethod org.codehaus.groovy.runtime.StringGroovyMethods padRight java.lang.String java.lang.Number java.lang.String +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods readLines java.lang.CharSequence +staticMethod org.codehaus.groovy.runtime.StringGroovyMethods readLines java.lang.String staticMethod org.codehaus.groovy.runtime.StringGroovyMethods replaceAll java.lang.CharSequence java.lang.CharSequence groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.StringGroovyMethods replaceAll java.lang.CharSequence java.util.regex.Pattern groovy.lang.Closure staticMethod org.codehaus.groovy.runtime.StringGroovyMethods replaceAll java.lang.String java.lang.String groovy.lang.Closure diff --git a/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/GroovyCallSiteSelectorTest.java b/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/GroovyCallSiteSelectorTest.java index 85e89801..ab362483 100644 --- a/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/GroovyCallSiteSelectorTest.java +++ b/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/GroovyCallSiteSelectorTest.java @@ -29,7 +29,6 @@ import groovy.lang.GString; import groovy.lang.Script; import org.codehaus.groovy.runtime.GStringImpl; -import org.junit.Assert; import org.junit.Test; import java.io.ByteArrayOutputStream; @@ -43,89 +42,54 @@ public class GroovyCallSiteSelectorTest { - @Test - public void arrays() throws Exception { + @Test public void arrays() throws Exception { Method m = EnumeratingWhitelistTest.C.class.getDeclaredMethod("m", Object[].class); - Assert.assertEquals("literal call", m, GroovyCallSiteSelector.method(new EnumeratingWhitelistTest.C(), "m", new Object[]{new Object[]{"a", "b"}})); - assertEquals("we assume the interceptor has dealt with varargs", null, GroovyCallSiteSelector.method(new EnumeratingWhitelistTest.C(), "m", new Object[]{"a", "b"})); - assertEquals("array cast", m, GroovyCallSiteSelector.method(new EnumeratingWhitelistTest.C(), "m", new Object[]{new String[]{"a", "b"}})); + assertEquals("literal call", m, GroovyCallSiteSelector.method(new EnumeratingWhitelistTest.C(), "m", new Object[] {new Object[] {"a", "b"}})); + assertEquals("we assume the interceptor has dealt with varargs", null, GroovyCallSiteSelector.method(new EnumeratingWhitelistTest.C(), "m", new Object[] {"a", "b"})); + assertEquals("array cast", m, GroovyCallSiteSelector.method(new EnumeratingWhitelistTest.C(), "m", new Object[] {new String[] {"a", "b"}})); } - @Test - public void overloads() throws Exception { + @Test public void overloads() throws Exception { PrintWriter receiver = new PrintWriter(new ByteArrayOutputStream()); - assertEquals(PrintWriter.class.getMethod("print", Object.class), GroovyCallSiteSelector.method(receiver, "print", new Object[]{new Object()})); - assertEquals(PrintWriter.class.getMethod("print", String.class), GroovyCallSiteSelector.method(receiver, "print", new Object[]{"message"})); - assertEquals(PrintWriter.class.getMethod("print", int.class), GroovyCallSiteSelector.method(receiver, "print", new Object[]{42})); + assertEquals(PrintWriter.class.getMethod("print", Object.class), GroovyCallSiteSelector.method(receiver, "print", new Object[] {new Object()})); + assertEquals(PrintWriter.class.getMethod("print", String.class), GroovyCallSiteSelector.method(receiver, "print", new Object[] {"message"})); + assertEquals(PrintWriter.class.getMethod("print", int.class), GroovyCallSiteSelector.method(receiver, "print", new Object[] {42})); } - @Test - public void methodsOnGString() throws Exception { - GStringImpl gString = new GStringImpl(new Object[0], new String[]{"x"}); - assertEquals(String.class.getMethod("substring", int.class), GroovyCallSiteSelector.method(gString, "substring", new Object[]{99})); +// @Issue("JENKINS-29541") + @Test public void methodsOnGString() throws Exception { + GStringImpl gString = new GStringImpl(new Object[0], new String[] {"x"}); + assertEquals(String.class.getMethod("substring", int.class), GroovyCallSiteSelector.method(gString, "substring", new Object[] {99})); assertEquals(GString.class.getMethod("getValues"), GroovyCallSiteSelector.method(gString, "getValues", new Object[0])); assertEquals(GString.class.getMethod("getStrings"), GroovyCallSiteSelector.method(gString, "getStrings", new Object[0])); } - @Test - public void primitives() throws Exception { - assertEquals(Primitives.class.getMethod("m1", long.class), GroovyCallSiteSelector.staticMethod(Primitives.class, "m1", new Object[]{Long.MAX_VALUE})); - assertEquals(Primitives.class.getMethod("m1", long.class), GroovyCallSiteSelector.staticMethod(Primitives.class, "m1", new Object[]{99})); - assertEquals(Primitives.class.getMethod("m2", long.class), GroovyCallSiteSelector.staticMethod(Primitives.class, "m2", new Object[]{Long.MAX_VALUE})); - assertEquals(Primitives.class.getMethod("m2", int.class), GroovyCallSiteSelector.staticMethod(Primitives.class, "m2", new Object[]{99})); +// @Issue("JENKINS-31701") + @Test public void primitives() throws Exception { + assertEquals(Primitives.class.getMethod("m1", long.class), GroovyCallSiteSelector.staticMethod(Primitives.class, "m1", new Object[] {Long.MAX_VALUE})); + assertEquals(Primitives.class.getMethod("m1", long.class), GroovyCallSiteSelector.staticMethod(Primitives.class, "m1", new Object[] {99})); + assertEquals(Primitives.class.getMethod("m2", long.class), GroovyCallSiteSelector.staticMethod(Primitives.class, "m2", new Object[] {Long.MAX_VALUE})); + assertEquals(Primitives.class.getMethod("m2", int.class), GroovyCallSiteSelector.staticMethod(Primitives.class, "m2", new Object[] {99})); } - public static class Primitives { - - public static void m1(long x) { - } - - public static void m2(int x) { - } - - public static void m2(long x) { - } + public static void m1(long x) {} + public static void m2(int x) {} + public static void m2(long x) {} } - @Test - public void staticMethodsCannotBeOverridden() throws Exception { - assertEquals(SandboxInterceptorTest.StaticTestExample.class.getMethod("getInstance"), GroovyCallSiteSelector.staticMethod(SandboxInterceptorTest.StaticTestExample.class, "getInstance", new Object[0])); + @Test public void staticMethodsCannotBeOverridden() throws Exception { + assertEquals(StaticTestExample.class.getMethod("getInstance"), GroovyCallSiteSelector.staticMethod(StaticTestExample.class, "getInstance", new Object[0])); } - @Test - public void main() throws Exception { +// @Issue("JENKINS-38908") + @Test public void main() throws Exception { Script receiver = (Script) new cd.go.contrib.plugins.configrepo.groovy.sandbox.GroovyScriptRunner(null).runScriptWithText("def main() {}; this"); assertEquals(receiver.getClass().getMethod("main"), GroovyCallSiteSelector.method(receiver, "main", new Object[0])); - assertEquals(receiver.getClass().getMethod("main", String[].class), GroovyCallSiteSelector.method(receiver, "main", new Object[]{"somearg"})); - } - - static class EnvVars { - - public EnvVars() { - - } - - public EnvVars(String... objects) { - } - - public EnvVars(List objects) { - - } - } - - class VarargsConstructor { - - public VarargsConstructor(String... objects) { - - } - - public VarargsConstructor(List objects) { - - } + assertEquals(receiver.getClass().getMethod("main", String[].class), GroovyCallSiteSelector.method(receiver, "main", new Object[] {"somearg"})); } - @Test - public void constructorVarargs() throws Exception { +// @Issue("JENKINS-45117") + @Test public void constructorVarargs() throws Exception { assertEquals(EnvVars.class.getConstructor(), GroovyCallSiteSelector.constructor(EnvVars.class, new Object[0])); assertEquals(EnvVars.class.getConstructor(String[].class), GroovyCallSiteSelector.constructor(EnvVars.class, new Object[]{"x"})); List params = new ArrayList<>(); @@ -143,6 +107,7 @@ public void constructorVarargs() throws Exception { new Object[]{String.class, "foo", Integer.class, Float.class})); } +// @Issue("JENKINS-47159") @Test public void varargsFailureCases() throws Exception { // If there's a partial match, we should get a ClassCastException @@ -158,10 +123,43 @@ public void varargsFailureCases() throws Exception { assertNull(GroovyCallSiteSelector.constructor(VarargsConstructor.class, new Object[]{"a", "b"})); } +//// @Issue("JENKINS-37257") @Test public void varargsArrayElementTypeMismatch() throws Exception { List l = Arrays.asList("a", "b", "c"); assertEquals(String.class.getMethod("join", CharSequence.class, Iterable.class), GroovyCallSiteSelector.staticMethod(String.class, "join", new Object[]{",", l})); } + + static class EnvVars { + + public EnvVars() { + + } + + public EnvVars(String... objects) { + } + + public EnvVars(List objects) { + + } + } + + static class StaticSubclassTestExample { + public static Object getInstance() { + return new Object(); + } + } + + class VarargsConstructor { + + public VarargsConstructor(String... objects) { + + } + + public VarargsConstructor(List objects) { + + } + } } + diff --git a/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/SandboxInterceptorTest.java b/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/SandboxInterceptorTest.java index abd66be5..2b7ccd61 100644 --- a/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/SandboxInterceptorTest.java +++ b/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/SandboxInterceptorTest.java @@ -33,6 +33,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.MultipleCompilationErrorsException; import org.codehaus.groovy.runtime.GStringImpl; import org.codehaus.groovy.runtime.IOGroovyMethods; import org.codehaus.groovy.runtime.InvokerHelper; @@ -40,7 +41,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ErrorCollector; +import org.junit.rules.ExpectedException; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URL; import java.text.DateFormat; @@ -48,23 +51,22 @@ import java.util.concurrent.Callable; import java.util.regex.Pattern; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; public class SandboxInterceptorTest { - @Rule - public ErrorCollector errors = new ErrorCollector(); + @Rule public ErrorCollector errors = new ErrorCollector(); - @Test - public void genericWhitelist() throws Exception { + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test public void genericWhitelist() throws Exception { assertEvaluate(new GenericWhitelist(), 3, "'foo bar baz'.split(' ').length"); assertEvaluate(new GenericWhitelist(), false, "def x = null; x != null"); } /** Checks that {@link GString} is handled sanely. */ - @Test - public void testGString() throws Exception { + @Test public void testGString() throws Exception { String clazz = Clazz.class.getName(); String script = "def x = 1; new " + clazz + "().method(\"foo${x}\")"; String expected = "-foo1"; @@ -73,8 +75,7 @@ public void testGString() throws Exception { } /** Checks that methods specifically expecting {@link GString} also work. */ - @Test - public void testGString2() throws Exception { + @Test public void testGString2() throws Exception { String clazz = Clazz.class.getName(); String script = "def x = 1; def c = new " + clazz + "(); c.quote(\"-${c.specialize(x)}-${x}-\")"; String expected = "-1-'1'-"; @@ -82,8 +83,8 @@ public void testGString2() throws Exception { assertEvaluate(new StaticWhitelist("new " + clazz, "method " + clazz + " specialize java.lang.Object", "method " + clazz + " quote java.lang.Object"), expected, script); } - @Test - public void substringGString() throws Exception { +// @Issue("JENKINS-29541") + @Test public void substringGString() throws Exception { assertEvaluate(new GenericWhitelist(), "hell", "'hello world'.substring(0, 4)"); assertEvaluate(new GenericWhitelist(), "hell", "def place = 'world'; \"hello ${place}\".substring(0, 4)"); } @@ -91,8 +92,7 @@ public void substringGString() throws Exception { /** * Tests the proper interception of builder-like method. */ - @Test - public void invokeMethod() throws Exception { + @Test public void invokeMethod() throws Exception { String script = "def builder = new groovy.json.JsonBuilder(); builder.point { x 5; y 3; }; builder.toString()"; String expected = "{\"point\":{\"x\":5,\"y\":3}}"; assertEvaluate(new BlanketWhitelist(), expected, script); @@ -127,8 +127,7 @@ public boolean permitsMethod(Method method, Object receiver, Object[] args) { } @Ignore("TODO there are various unhandled cases, such as Closure → SAM, or numeric conversions, or number → String, or boxing/unboxing.") - @Test - public void testNumbers() throws Exception { + @Test public void testNumbers() throws Exception { String clazz = Clazz.class.getName(); String script = "int x = 1; " + clazz + ".incr(x)"; Long expected = 2L; @@ -138,15 +137,14 @@ public void testNumbers() throws Exception { assertEvaluate(new StaticWhitelist("staticMethod " + clazz + " incr java.lang.Long"), expected, script); } - @Test - public void staticFields() throws Exception { + @Test public void staticFields() throws Exception { String clazz = Clazz.class.getName(); assertEvaluate(new StaticWhitelist("staticField " + clazz + " flag"), true, clazz + ".flag=true"); assertTrue(Clazz.flag); } - @Test - public void finalFields() throws Exception { +// @Issue("JENKINS-34599") + @Test public void finalFields() throws Exception { // Control cases: non-final fields. assertEvaluate(new ProxyWhitelist(), 99, "class X {int x = 99}; new X().x"); assertEvaluate(new ProxyWhitelist(), 99, "class X {int x; X(int x) {this.x = x}}; new X(99).x"); @@ -187,8 +185,7 @@ public void finalFields() throws Exception { assertRejected(new AnnotatedWhitelist(), "method " + sps + " setSecure boolean", "class X extends " + sps + " {X() {this.secure = false}}; new X()"); } - @Test - public void propertiesAndGettersAndSetters() throws Exception { + @Test public void propertiesAndGettersAndSetters() throws Exception { String clazz = Clazz.class.getName(); assertEvaluate(new StaticWhitelist("new " + clazz, "field " + clazz + " prop"), "default", "new " + clazz + "().prop"); assertEvaluate(new StaticWhitelist("new " + clazz, "method " + clazz + " getProp"), "default", "new " + clazz + "().prop"); @@ -211,14 +208,14 @@ public void propertiesAndGettersAndSetters() throws Exception { fail(); } catch (RejectedAccessException x) { assertEquals(null, x.getSignature()); - assertEquals("unclassified field " + clazz + " nonexistent", x.getMessage()); + assertEquals("No such field found: field " + clazz + " nonexistent", x.getMessage()); } try { evaluate(new StaticWhitelist("new " + clazz), "new " + clazz + "().nonexistent = 'edited'"); fail(); } catch (RejectedAccessException x) { assertEquals(null, x.getSignature()); - assertEquals("unclassified field " + clazz + " nonexistent", x.getMessage()); + assertEquals("No such field found: field " + clazz + " nonexistent", x.getMessage()); } assertRejected(new StaticWhitelist("new " + clazz), "method " + clazz + " getProp5", "new " + clazz + "().prop5"); assertEvaluate(new StaticWhitelist("new " + clazz, "method " + clazz + " getProp5"), "DEFAULT", "new " + clazz + "().prop5"); @@ -231,25 +228,13 @@ public void propertiesAndGettersAndSetters() throws Exception { } public static final class Clazz { - static boolean flag; - - @Whitelisted - public Clazz() { - } - - @Whitelisted - public String method(String x) { - return "-" + x; - } - - @Whitelisted - Special specialize(Object o) { + @Whitelisted public Clazz() {} + @Whitelisted public String method(String x) {return "-" + x;} + @Whitelisted Special specialize(Object o) { return new Special(o); } - - @Whitelisted - String quote(Object o) { + @Whitelisted String quote(Object o) { if (o instanceof GString) { GString gs = (GString) o; Object[] values = gs.getValues(); @@ -265,67 +250,49 @@ String quote(Object o) { return quoteSingle(o); } } - private String quoteSingle(Object o) { return "'" + String.valueOf(o) + "'"; } - - @Whitelisted - static long incr(long x) { + @Whitelisted static long incr(long x) { return x + 1; } - private String prop = "default"; - public String getProp() { return prop; } - public void setProp(String prop) { this.prop = prop; } - private String _prop2 = "default"; - public String getProp2() { return _prop2; } - public void setProp2(String prop2) { this._prop2 = prop2; } - private boolean _prop3; - public boolean isProp3() { return _prop3; } - public void setProp3(boolean prop3) { this._prop3 = prop3; } - public static boolean isProp4() { return true; } - private String prop5 = "default"; - public String getProp5() { return prop5.toUpperCase(Locale.ENGLISH); } - public void setProp5(String value) { prop5 = value.toLowerCase(Locale.ENGLISH); } - public String rawProp5() { return prop5; } } - @Test - public void dynamicProperties() throws Exception { + @Test public void dynamicProperties() throws Exception { String dynamic = Dynamic.class.getName(); String ctor = "new " + dynamic; String getProperty = "method groovy.lang.GroovyObject getProperty java.lang.String"; @@ -337,36 +304,28 @@ public void dynamicProperties() throws Exception { } public static final class Dynamic extends GroovyObjectSupport { - - private final Map values = new HashMap(); - - @Override - public Object getProperty(String n) { + private final Map values = new HashMap(); + @Override public Object getProperty(String n) { return values.get(n); } - - @Override - public void setProperty(String n, Object v) { + @Override public void setProperty(String n, Object v) { values.put(n, v); } } - @Test - public void mapProperties() throws Exception { + @Test public void mapProperties() throws Exception { assertEvaluate(new GenericWhitelist(), 42, "def m = [:]; m.answer = 42; m.answer"); } public static final class Special { - final Object o; - Special(Object o) { this.o = o; } } - @Test - public void defaultGroovyMethods() throws Exception { +// @Issue({"JENKINS-25119", "JENKINS-27725", "JENKINS-57299"}) + @Test public void defaultGroovyMethods() throws Exception { assertRejected(new ProxyWhitelist(), "staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods toInteger java.lang.String", "'123'.toInteger();"); assertEvaluate(new GenericWhitelist(), 123, "'123'.toInteger();"); assertEvaluate(new GenericWhitelist(), Arrays.asList(1, 4, 9), "([1, 2, 3] as int[]).collect({x -> x * x})"); @@ -379,10 +338,15 @@ public void defaultGroovyMethods() throws Exception { assertEvaluate(new GenericWhitelist(), true, "'42'.number"); // TODO should also cover set* methods, though these seem rare // TODO check DefaultGroovyStaticMethods also (though there are few useful & safe calls there) + // cover drop and dropRight methods: + assertEvaluate(new GenericWhitelist(), Arrays.asList(2, 3, 4), "[1, 2, 3, 4].drop(1)"); + assertEvaluate(new GenericWhitelist(), Arrays.asList(1, 2, 3), "[1, 2, 3, 4].dropRight(1)"); + // cover take and takeRight methods: + assertEvaluate(new GenericWhitelist(), Arrays.asList(1, 2), "[1, 2, 3, 4].take(2)"); + assertEvaluate(new GenericWhitelist(), Arrays.asList(3, 4), "[1, 2, 3, 4].takeRight(2)"); } - @Test - public void whitelistedIrrelevantInsideScript() throws Exception { + @Test public void whitelistedIrrelevantInsideScript() throws Exception { String clazz = Unsafe.class.getName(); String wl = Whitelisted.class.getName(); // @Whitelisted does not grant us access to anything new: @@ -393,13 +357,12 @@ public void whitelistedIrrelevantInsideScript() throws Exception { assertRejected(new AnnotatedWhitelist(), "staticMethod " + clazz + " explode", "C.m(); class C {static void m() {" + clazz + ".explode();}}"); } - @Test - public void structConstructor() throws Exception { +// @Issue("JENKINS-34741") + @Test public void structConstructor() throws Exception { assertEvaluate(new StaticWhitelist(), "ok", "class C {String f}; new C(f: 'ok').f"); } - @Test - public void defSyntax() throws Exception { + @Test public void defSyntax() throws Exception { String clazz = Unsafe.class.getName(); Whitelist w = new ProxyWhitelist(new AnnotatedWhitelist(), /* for some reason def syntax triggers this */new StaticWhitelist("method java.util.Collection toArray")); assertEvaluate(w, "ok", "m(); def m() {" + clazz + ".ok()}"); @@ -408,22 +371,14 @@ public void defSyntax() throws Exception { } public static final class Unsafe { - - @Whitelisted - public static String ok() { - return "ok"; - } - - public static void explode() { - } - - private Unsafe() { - } + @Whitelisted public static String ok() {return "ok";} + public static void explode() {} + private Unsafe() {} } /** Expect errors from {@link org.codehaus.groovy.runtime.NullObject}. */ - @Test - public void nullPointerException() throws Exception { +// @Issue("kohsuke/groovy-sandbox #15") + @Test public void nullPointerException() throws Exception { try { evaluate(new ProxyWhitelist(), "def x = null; x.member"); fail(); @@ -446,15 +401,14 @@ public void nullPointerException() throws Exception { /** * Tests the method invocation / property access through closures. - *

+ * *

* Groovy closures act as a proxy when it comes to property/method access. Based on the configuration, it can * access those from some combination of owner/delegate. As this is an important building block for custom DSL, * script-security understands this logic and checks access at the actual target of the proxy, so that Closures * can be used safely. */ - @Test - public void closureDelegate() throws Exception { + @Test public void closureDelegate() throws Exception { ProxyWhitelist rules = new ProxyWhitelist(new StaticWhitelist( "new java.lang.Exception java.lang.String", "method java.util.concurrent.Callable call", @@ -506,23 +460,22 @@ public void closureDelegate() throws Exception { } } - @Test - public void metaClassDelegate() throws Exception { + @Test public void metaClassDelegate() throws Exception { new GroovyShell().evaluate("String.metaClass.getAnswer = {-> return 42}"); // privileged operation assertEvaluate(new StaticWhitelist(), 42, "'existence'.getAnswer()"); assertEvaluate(new StaticWhitelist(), 42, "'existence'.answer"); assertRejected(new GenericWhitelist(), "staticMethod java.lang.System exit int", "def c = System.&exit; c(1)"); } - @Test - public void curry() throws Exception { +// @Issue("JENKINS-28277") + @Test public void curry() throws Exception { assertEvaluate(new GenericWhitelist(), 'h', "def charAt = {idx, str -> str.charAt(idx)}; def firstChar = charAt.curry(0); firstChar 'hello'"); assertEvaluate(new GenericWhitelist(), 'h', "def charOfHello = 'hello'.&charAt; def firstCharOfHello = charOfHello.curry(0); firstCharOfHello()"); assertEvaluate(new GenericWhitelist(), 'h', "def charAt = {str, idx -> str.charAt(idx)}; def firstChar = charAt.ncurry(1, 0); firstChar 'hello'"); } - @Test - public void varargs() throws Exception { +// @Issue("JENKINS-34739") + @Test public void varargs() throws Exception { // Control cases: ProxyWhitelist wl = new ProxyWhitelist(new GenericWhitelist(), new AnnotatedWhitelist()); assertEvaluate(wl, 0, "class UsesVarargs {static int len(String... vals) {vals.length}}; UsesVarargs.len(new String[0])"); @@ -550,14 +503,11 @@ public void varargs() throws Exception { assertRejected(wl, "staticMethod " + uv + " explode java.lang.String[]", uv + ".explode()"); assertRejected(wl, "staticMethod " + uv + " explode java.lang.String[]", uv + ".explode('one', 'two', 'three')"); } - public static class UsesVarargs { - @Whitelisted public static int len(String... vals) { return vals.length; } - @Whitelisted public static int sum(int... numbers) { int sum = 0; @@ -566,20 +516,15 @@ public static int sum(int... numbers) { } return sum; } - @Whitelisted public static int xlen(int x, String... vals) { return x + vals.length; } - @Whitelisted public static String join(String sep, String... vals) { return StringUtils.join(vals, sep); } - - public static void explode(String... vals) { - } - + public static void explode(String... vals) {} @Whitelisted public static String varargsMethod(Integer i, Boolean b, StringContainer... s) { return i.toString() + "-" + b.toString() + "-" + StringUtils.join(s, "-"); @@ -587,7 +532,6 @@ public static String varargsMethod(Integer i, Boolean b, StringContainer... s) { } public static final class StringContainer { - final String o; @Whitelisted @@ -602,25 +546,21 @@ public String toString() { } } - @Test - public void templates() throws Exception { + @Test public void templates() throws Exception { final GroovyShell shell = new GroovyShell(GroovySandbox.createSecureCompilerConfiguration()); final Template t = new SimpleTemplateEngine(shell).createTemplate("goodbye <%= aspect.toLowerCase() %> world"); assertEquals("goodbye cruel world", GroovySandbox.runInSandbox(new Callable() { - @Override - public String call() throws Exception { - return t.make(new HashMap(Collections.singletonMap("aspect", "CRUEL"))).toString(); + @Override public String call() throws Exception { + return t.make(new HashMap(Collections.singletonMap("aspect", "CRUEL"))).toString(); } }, new ProxyWhitelist(new StaticWhitelist("method java.lang.String toLowerCase"), new GenericWhitelist()))); } - @Test - public void selfProperties() throws Exception { + @Test public void selfProperties() throws Exception { assertEvaluate(new ProxyWhitelist(), true, "BOOL=true; BOOL"); } - @Test - public void missingPropertyException() throws Exception { + @Test public void missingPropertyException() throws Exception { try { evaluate(new ProxyWhitelist(), "GOOP"); fail(); @@ -629,16 +569,17 @@ public void missingPropertyException() throws Exception { } } - @Test - public void specialScript() throws Exception { + @Test public void specialScript() throws Exception { CompilerConfiguration cc = GroovySandbox.createSecureCompilerConfiguration(); cc.setScriptBaseClass(SpecialScript.class.getName()); GroovyShell shell = new GroovyShell(cc); Whitelist wl = new AbstractWhitelist() { - @Override - public boolean permitsMethod(Method method, Object receiver, Object[] args) { + @Override public boolean permitsMethod(Method method, Object receiver, Object[] args) { return method.getDeclaringClass() == GroovyObject.class && method.getName().equals("getProperty") && receiver instanceof SpecialScript && args[0].equals("magic"); } + @Override public boolean permitsConstructor(Constructor constructor, Object[] args) { + return constructor.getDeclaringClass() == SpecialScript.class; + } }; assertEquals(42, GroovySandbox.run(shell.parse("magic"), wl)); try { @@ -647,11 +588,8 @@ public boolean permitsMethod(Method method, Object receiver, Object[] args) { assertEquals("boring", x.getProperty()); } } - public static abstract class SpecialScript extends Script { - - @Override - public Object getProperty(String property) { + @Override public Object getProperty(String property) { if (property.equals("magic")) { return 42; } @@ -659,9 +597,8 @@ public Object getProperty(String property) { } } - @Ignore("TODO last fails with: RejectedAccessException: Scripts not permitted to use new java.util.Properties java.util.Properties") - @Test - public void properties() throws Exception { +// @Issue("JENKINS-46757") + @Test public void properties() throws Exception { String script = "def properties = new Properties()"; assertRejected(new StaticWhitelist(), "new java.util.Properties", script); assertEvaluate(new StaticWhitelist("new java.util.Properties"), new Properties(), script); @@ -670,16 +607,25 @@ public void properties() throws Exception { assertEvaluate(new StaticWhitelist("new java.util.Properties"), new Properties(), script); } - @Test - public void typeCoercion() throws Exception { +// @Issue({"SECURITY-566", "SECURITY-1353"}) + @Test public void typeCoercion() throws Exception { assertRejected(new StaticWhitelist("staticMethod java.util.Locale getDefault"), "method java.util.Locale getCountry", "interface I {String getCountry()}; (Locale.getDefault() as I).getCountry()"); assertRejected(new StaticWhitelist("staticMethod java.util.Locale getDefault"), "method java.util.Locale getCountry", "interface I {String getCountry()}; (Locale.getDefault() as I).country"); assertRejected(new ProxyWhitelist(), "staticMethod java.util.Locale getAvailableLocales", "interface I {Locale[] getAvailableLocales()}; (Locale as I).getAvailableLocales()"); assertRejected(new ProxyWhitelist(), "staticMethod java.util.Locale getAvailableLocales", "interface I {Locale[] getAvailableLocales()}; (Locale as I).availableLocales"); + assertEvaluate(new StaticWhitelist("staticMethod java.lang.Math max int int"), 3.0d, "(double) Math.max(2, 3)"); + assertEvaluate(new StaticWhitelist("staticMethod java.lang.Math max int int"), 3.0d, "Math.max(2, 3) as double"); + assertEvaluate(new StaticWhitelist("staticMethod java.lang.Math max int int"), 3.0d, "double x = Math.max(2, 3); x"); + assertRejected(new GenericWhitelist(), "staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter asType java.lang.Object java.lang.Class", + "def f = org.codehaus.groovy.runtime.ScriptBytecodeAdapter.asType(['/tmp'], File); echo(/$f/)"); + assertRejected(new GenericWhitelist(), "staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter castToType java.lang.Object java.lang.Class", + "def f = org.codehaus.groovy.runtime.ScriptBytecodeAdapter.castToType(['/tmp'], File); echo(/$f/)"); + assertRejected(new GenericWhitelist(), "new java.io.File java.lang.String", + "def f = org.kohsuke.groovy.sandbox.impl.Checker.checkedCast(File, ['/tmp'], true, false, false); echo(/$f/)"); } - @Test - public void positionalConstructors() throws Exception { +// @Issue("SECURITY-580") + @Test public void positionalConstructors() throws Exception { assertRejected(new ProxyWhitelist(), "new java.lang.Boolean java.lang.String", "['true'] as Boolean"); assertEvaluate(new StaticWhitelist("new java.lang.Boolean java.lang.String"), true, "['true'] as Boolean"); String cc = "staticMethod org.kohsuke.groovy.sandbox.impl.Checker checkedCast java.lang.Class java.lang.Object boolean boolean boolean"; @@ -710,13 +656,13 @@ public void positionalConstructors() throws Exception { */ } - @Test - public void infiniteLoop() throws Exception { +// @Issue("kohsuke/groovy-sandbox #16") + @Test public void infiniteLoop() throws Exception { assertEvaluate(new BlanketWhitelist(), "abc", "def split = 'a b c'.split(' '); def b = new StringBuilder(); for (i = 0; i < split.length; i++) {println(i); b.append(split[i])}; b.toString()"); } - @Test - public void primitiveTypes() throws Exception { +// @Issue("JENKINS-25118") + @Test public void primitiveTypes() throws Exception { // Some String operations: assertRejected(new ProxyWhitelist(), "method java.lang.CharSequence charAt int", "'123'.charAt(1);"); assertEvaluate(new StaticWhitelist("method java.lang.CharSequence charAt int"), '2', "'123'.charAt(1);"); @@ -731,55 +677,42 @@ public void primitiveTypes() throws Exception { assertEvaluate(new GenericWhitelist(), "23", "'2' + 3"); } - @Test - public void ambiguousOverloads() { + @Test public void ambiguousOverloads() { try { evaluate(new AnnotatedWhitelist(), Ambiguity.class.getName() + ".m(null)"); fail("Ambiguous overload is an error in Groovy 2"); - } catch (GroovyRuntimeException e) { + } catch(GroovyRuntimeException e) { // OK } } public static final class Ambiguity { - - @Whitelisted - public static boolean m(String x) { - return true; - } - - @Whitelisted - public static boolean m(URL x) { - return true; - } + @Whitelisted public static boolean m(String x) {return true;} + @Whitelisted public static boolean m(URL x) {return true;} } - @Test - public void regexps() throws Exception { + @Test public void regexps() throws Exception { assertEvaluate(new GenericWhitelist(), "goodbye world", "def text = 'hello world'; def matcher = text =~ 'hello (.+)'; matcher ? \"goodbye ${matcher[0][1]}\" : 'fail'"); } - @Test - public void splitAndJoin() throws Exception { + @Test public void splitAndJoin() throws Exception { assertEvaluate(new GenericWhitelist(), Collections.singletonMap("part0", "one\ntwo"), "def list = [['one', 'two']]; def map = [:]; for (int i = 0; i < list.size(); i++) {map[\"part${i}\"] = list.get(i).join(\"\\n\")}; map"); } public static class ClassWithInvokeMethod extends GroovyObjectSupport { - @Override public Object invokeMethod(String name, Object args) { throw new IllegalStateException(); } } - @Test - public void invokeMethod_vs_DefaultGroovyMethods() throws Exception { + @Test public void invokeMethod_vs_DefaultGroovyMethods() throws Exception { // Closure defines the invokeMethod method, and asBoolean is defined on DefaultGroovyMethods. // the method dispatching in this case is that c.asBoolean() resolves to DefaultGroovyMethods.asBoolean() // and not invokeMethod("asBoolean") // calling asBoolean shouldn't go through invokeMethod - MetaMethod m1 = InvokerHelper.getMetaClass(ClassWithInvokeMethod.class).pickMethod("asBoolean", new Class[0]); + MetaMethod m1 = InvokerHelper.getMetaClass(ClassWithInvokeMethod.class).pickMethod("asBoolean",new Class[0]); assertNotNull(m1); assertTrue((Boolean) m1.invoke(new ClassWithInvokeMethod(), new Object[0])); @@ -794,8 +727,8 @@ public void invokeMethod_vs_DefaultGroovyMethods() throws Exception { ); } - @Test - public void superCalls() throws Exception { +// @Issue({"JENKINS-42563", "SECURITY-582"}) + @Test public void superCalls() throws Exception { String sps = SafePerSe.class.getName(); assertRejected(new AnnotatedWhitelist(), "method " + sps + " dangerous", "class C extends " + sps + " {void dangerous() {super.dangerous()}}; new C().dangerous()"); assertRejected(new AnnotatedWhitelist(), "method " + sps + " dangerous", "class C extends " + sps + " {void x() {super.dangerous()}}; new C().x()"); @@ -811,51 +744,41 @@ public void superCalls() throws Exception { assertRejected(new StaticWhitelist("method java.lang.Object toString", "new java.lang.Exception java.lang.String"), "method java.lang.String toUpperCase", "class X6 extends Exception {X6(String x) {super(x.toUpperCase())}}; new X6('x')"); assertRejected(new StaticWhitelist("method java.lang.Object toString", "new java.lang.Exception"), "new java.lang.Object", "class X7 extends Exception {X7(String x) {new Object()}}; new X7('x')"); } - public static class SafePerSe { - @Whitelisted - public SafePerSe() { - } - - public void dangerous() { - } - - public void setSecure(boolean x) { - } + public SafePerSe() {} + public void dangerous() {} + public void setSecure(boolean x) {} } - @Test - public void keywordsAndOperators() throws Exception { + @Test public void keywordsAndOperators() throws Exception { String script = IOGroovyMethods.getText(this.getClass().getResourceAsStream("SandboxInterceptorTest/all.groovy"), "utf-8"); assertEvaluate(new GenericWhitelist(), null, script); } - @Test - public void calendarGetInstance() throws Exception { +// @Issue("JENKINS-31234") + @Test public void calendarGetInstance() throws Exception { assertEvaluate(new GenericWhitelist(), true, "Calendar.getInstance().get(Calendar.DAY_OF_MONTH) < 32"); assertEvaluate(new GenericWhitelist(), true, "Calendar.instance.get(Calendar.DAY_OF_MONTH) < 32"); } - @Test - public void primitiveWidening() throws Exception { +// @Issue("JENKINS-31701") + @Test public void primitiveWidening() throws Exception { assertEvaluate(new AnnotatedWhitelist(), 4L, SandboxInterceptorTest.class.getName() + ".usePrimitive(2)"); } - - @Whitelisted - public static long usePrimitive(long x) { + @Whitelisted public static long usePrimitive(long x) { return x + 2; } - @Test - public void tokenize() throws Exception { +// @Issue("JENKINS-32211") + @Test public void tokenize() throws Exception { assertEvaluate(new GenericWhitelist(), 3, "'foo bar baz'.tokenize().size()"); assertEvaluate(new GenericWhitelist(), 3, "'foo bar baz'.tokenize(' ').size()"); assertEvaluate(new GenericWhitelist(), 3, "'foo bar baz'.tokenize('ba').size()"); } - @Test - public void enums() throws Exception { +// @Issue("JENKINS-33023") + @Test public void enums() throws Exception { String script = "enum Thing {\n" + " FIRST(\"The first thing\");\n" + " String description;\n" @@ -873,47 +796,50 @@ public void enums() throws Exception { assertEvaluate(wl, "TWO", e + ".TWO.name()"); assertRejected(wl, "staticField " + e + " ONE", e + ".ONE.name()"); } - public enum E { ONE(1), @Whitelisted TWO(2); - private final int n; - private E(int n) { this.n = n; } - @Whitelisted public int getN() { return n; } + public void explode() {} + } - public void explode() { - } + @Test public void staticMethodsCannotBeOverridden() throws Exception { + assertRejected(new StaticWhitelist(), "staticMethod " + StaticTestExample.class.getName() + " getInstance", StaticTestExample.class.getName() + ".getInstance()"); + assertRejected(new StaticWhitelist(), "staticMethod " + StaticTestExample.class.getName() + " getInstance", StaticTestExample.class.getName() + ".instance"); + assertRejected(new StaticWhitelist(), "staticMethod " + GroovyCallSiteSelectorTest.StaticSubclassTestExample.class.getName() + " getInstance", GroovyCallSiteSelectorTest.StaticSubclassTestExample.class.getName() + ".getInstance()"); + assertRejected(new StaticWhitelist(), "staticMethod " + GroovyCallSiteSelectorTest.StaticSubclassTestExample.class.getName() + " getInstance", GroovyCallSiteSelectorTest.StaticSubclassTestExample.class.getName() + ".instance"); } - static class StaticTestExample { +// @Issue("SECURITY-1266") + @Test + public void blockedASTTransformsASTTest() throws Exception { + GroovyShell shell = new GroovyShell(GroovySandbox.createSecureCompilerConfiguration()); - public static Object getInstance() { - return new Object(); - } - } + thrown.expect(MultipleCompilationErrorsException.class); + thrown.expectMessage("Annotation ASTTest cannot be used in the sandbox"); - static class StaticSubclassTestExample { - public static Object getInstance() { - return new Object(); - } + shell.parse("import groovy.transform.*\n" + + "@ASTTest(value={ assert true })\n" + + "@Field int x\n"); } +// @Issue("SECURITY-1266") @Test - public void staticMethodsCannotBeOverridden() throws Exception { - assertRejected(new StaticWhitelist(), "staticMethod " + StaticTestExample.class.getName() + " getInstance", StaticTestExample.class.getName() + ".getInstance()"); - assertRejected(new StaticWhitelist(), "staticMethod " + StaticTestExample.class.getName() + " getInstance", StaticTestExample.class.getName() + ".instance"); + public void blockedASTTransformsGrab() throws Exception { + GroovyShell shell = new GroovyShell(GroovySandbox.createSecureCompilerConfiguration()); + thrown.expect(MultipleCompilationErrorsException.class); + thrown.expectMessage("Annotation Grab cannot be used in the sandbox"); - assertRejected(new StaticWhitelist(), "staticMethod " + StaticSubclassTestExample.class.getName() + " getInstance", StaticSubclassTestExample.class.getName() + ".getInstance()"); - assertRejected(new StaticWhitelist(), "staticMethod " + StaticSubclassTestExample.class.getName() + " getInstance", StaticSubclassTestExample.class.getName() + ".instance"); + shell.parse("@Grab(group='foo', module='bar', version='1.0')\n" + + "def foo\n"); } private static Object evaluate(Whitelist whitelist, String script) { @@ -962,52 +888,59 @@ public static void assertRejected(Whitelist whitelist, String expectedSignature, } } - @Test - public void methodMissingException() throws Exception { +// @Issue("JENKINS-37129") + @Test public void methodMissingException() throws Exception { // test: trying to call a nonexistent method try { evaluate(new GenericWhitelist(), "[].noSuchMethod()"); fail(); } catch (MissingMethodException e) { - assertEquals(e.getType(), ArrayList.class); - assertThat(e.getMethod(), is("noSuchMethod")); + assertEquals(e.getType(),ArrayList.class); + assertThat(e.getMethod(),is("noSuchMethod")); } // control: trying to call an existing method that's not safe assertRejected(new GenericWhitelist(), "method java.lang.Class getClassLoader", "[].class.classLoader"); } +// @Issue("JENKINS-46088") @Test public void matcherTypeAssignment() throws Exception { assertEvaluate(new GenericWhitelist(), "goodbye world", "def text = 'hello world'; java.util.regex.Matcher matcher = text =~ 'hello (.+)'; matcher ? \"goodbye ${matcher[0][1]}\" : 'fail'"); } +// @Issue("JENKINS-46088") @Test public void rhsOfDeclarationTransformed() throws Exception { - assertRejected(new StaticWhitelist(), "staticMethod " + StaticTestExample.class.getName() + " getInstance", StaticTestExample.class.getName() + " someVar = " +StaticTestExample.class.getName() + ".getInstance()"); + assertRejected(new StaticWhitelist(), "staticMethod " + StaticTestExample.class.getName() + " getInstance", StaticTestExample.class.getName() + " someVar = " + StaticTestExample.class.getName() + ".getInstance()"); } +// @Issue("JENKINS-46191") @Test public void emptyDeclaration() throws Exception { assertEvaluate(new GenericWhitelist(), "abc", "String a; a = 'abc'; return a"); } +// @Issue("JENKINS-46358") @Test public void validFromAnyDGMClass() throws Exception { // This verifies that we pick up a valid DGM-style method from a class other than DefaultGroovyMethods assertEvaluate(new GenericWhitelist(), "alppe", "String a = 'apple'; return a.replaceFirst('ppl') { it.reverse() }"); } +// @Issue("JENKINS-46391") @Test public void newPattern() throws Exception { assertEvaluate(new GenericWhitelist(), true, "def f = java.util.regex.Pattern.compile('f.*'); return f.matcher('foo').matches()"); } +// @Issue("JENKINS-46391") @Test public void tildePattern() throws Exception { assertEvaluate(new GenericWhitelist(), Pattern.class, "def f = ~/f.*/; return f.class"); } +// @Issue("JENKINS-35294") @Test public void enumWithVarargs() throws Exception { String script = "enum Thing {\n" @@ -1022,6 +955,7 @@ public void enumWithVarargs() throws Exception { assertEvaluate(new GenericWhitelist(), expected, script); } +// @Issue("JENKINS-35294") @Test public void enumWithStringAndVarargs() throws Exception { String script = "enum Thing {\n" @@ -1036,6 +970,7 @@ public void enumWithStringAndVarargs() throws Exception { assertEvaluate(new GenericWhitelist(), expected, script); } +// @Issue("JENKINS-44557") @Test public void varArgsWithGString() throws Exception { ProxyWhitelist wl = new ProxyWhitelist(new GenericWhitelist(), new AnnotatedWhitelist()); @@ -1044,6 +979,7 @@ public void varArgsWithGString() throws Exception { assertEvaluate(wl, 3, "def twoStr = 'two'; " + uv + ".len('one', \"${twoStr}\", 'three')"); } +// @Issue("JENKINS-47893") @Test public void varArgsWithOtherArgs() throws Exception { ProxyWhitelist wl = new ProxyWhitelist(new GenericWhitelist(), new AnnotatedWhitelist()); @@ -1058,6 +994,7 @@ public void varArgsWithOtherArgs() throws Exception { assertEvaluate(wl, expected, script); } +// @Issue("JENKINS-48364") @Test public void nullFirstVarArg() throws Exception { ProxyWhitelist wl = new ProxyWhitelist(new GenericWhitelist(), new AnnotatedWhitelist()); @@ -1068,6 +1005,7 @@ public void nullFirstVarArg() throws Exception { assertEvaluate(wl, expected, script); } +// @Issue("JENKINS-46213") @Test public void varArgsOnStaticDeclaration() throws Exception { String script = "class Explode {\n" + @@ -1091,12 +1029,14 @@ public void varArgsOnStaticDeclaration() throws Exception { script); } +// @Issue("SECURITY-663") @Test public void castAsFile() throws Exception { assertRejected(new GenericWhitelist(), "new java.io.File java.lang.String", "def s = []; ('/tmp/foo' as File).each { s << it }\n"); } +// @Issue("JENKINS-48501") @Test public void nullInVarArgsAsArray() throws Exception { String script = "def TEST_FMT = 'a:%s b:%s c:%s d:%s'\n" + @@ -1106,4 +1046,193 @@ public void nullInVarArgsAsArray() throws Exception { "a:null b:2 c:3 d:4", script); } + + public static class NonArrayConstructorList extends ArrayList { + public NonArrayConstructorList(boolean choiceOne, boolean choiceTwo) { + if (choiceOne) { + this.add("one"); + } + if (choiceTwo) { + this.add("two"); + } + } + } + +// @Issue("JENKINS-50380") + @Test + public void checkedCastWhenAssignable() throws Exception { + String nacl = NonArrayConstructorList.class.getName(); + // pre groovy-sandbox-1.18, results in unclassified new org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptorTest$NonArrayConstructorList java.lang.String + assertEvaluate(new StaticWhitelist("new " + SandboxInterceptorTest.class.getName() + + "$NonArrayConstructorList boolean boolean", + "staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods join java.util.Collection java.lang.String"), + "one", + nacl + " foo = new " + nacl + "(true, false); return foo.join('')"); + + } + + public static class SimpleNamedBean { + private String name; + + @Whitelisted + public SimpleNamedBean(String n) { + this.name = n; + } + + @Whitelisted + public String getName() { + return name; + } + + // This is not whitelisted for test purposes to be sure we still do checks. + public String getOther() { + return name; + } + } + +// @Issue("JENKINS-50470") + @Test + public void checkedGetPropertyOnCollection() throws Exception { + String snb = SimpleNamedBean.class.getName(); + + // Before JENKINS-50470 fix, this would error out on "unclassified field java.util.ArrayList name" + assertEvaluate(new AnnotatedWhitelist(), Arrays.asList("a", "b", "c"), + "def l = [new " + snb + "('a'), new " + snb +"('b'), new " + snb + "('c')]\n" + + "return l.name\n"); + + // We should still be calling checkedGetProperty properly for the objects within the collection. + assertRejected(new AnnotatedWhitelist(), "method " + SandboxInterceptorTest.class.getName() + + "$SimpleNamedBean getOther", + "def l = [new " + snb + "('a'), new " + snb +"('b'), new " + snb + "('c')]\n" + + "return l.other\n"); + } + +// @Issue("JENKINS-50843") + @Test + public void callClosureElementOfMapAsMethod() throws Exception { + assertEvaluate(new GenericWhitelist(), "hello", "def m = [ f: {return 'hello'} ]; m.f()"); + assertEvaluate(new GenericWhitelist(), 15, "def m = [ f: {a -> return a*3} ]; m.f(5)"); + assertEvaluate(new GenericWhitelist(), "a=hello,b=10", "def m = [ f: {a,b -> return \"a=${a},b=${b}\"} ]; m.f('hello',10)"); + assertEvaluate(new GenericWhitelist(), 2, "def m = [ f: {it.size()} ]; m.f(foo:0, bar:1)"); + } + +// @Issue("JENKINS-50906") + @Test + public void scriptBindingClosureVariableCall() throws Exception { + assertEvaluate(new GenericWhitelist(), true, "def func = { 1 }; this.func2 = { 1 }; return func() == func2();\n"); + assertEvaluate(new GenericWhitelist(), true, "def func = { x -> x }; this.func2 = { x -> x }; return func(5) == func2(5);\n"); + assertEvaluate(new GenericWhitelist(), true, "def func = { x, y -> x * y }; this.func2 = { x, y -> x * y }; return func(4, 5) == func2(4, 5);\n"); + assertEvaluate(new GenericWhitelist(), true, "def func = { it }; this.func2 = { it }; return func(12) == func2(12);\n"); + } + + @Test + public void dateTimeApi() throws Exception { + assertEvaluate(new GenericWhitelist(), 8, "def tomorrow = java.time.LocalDate.now().plusDays(1).format(java.time.format.DateTimeFormatter.BASIC_ISO_DATE).length()"); + assertEvaluate(new GenericWhitelist(), "2017-01-06", "def yesterday = java.time.LocalDate.parse('2017-01-07').minusDays(1).format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)"); + assertEvaluate(new GenericWhitelist(), 15, "java.time.LocalTime.now().withHour(15).getHour()"); + assertEvaluate(new GenericWhitelist(), "20:42:00", "java.time.LocalTime.parse('23:42').minusHours(3).format(java.time.format.DateTimeFormatter.ISO_LOCAL_TIME)"); + assertEvaluate(new GenericWhitelist(), 15, "java.time.LocalDateTime.now().withMinute(15).minute"); + assertEvaluate(new GenericWhitelist(), "2007-12-03T07:15:30", "java.time.LocalDateTime.parse('2007-12-03T10:15:30').minusHours(3).format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME)"); + } + +// @Issue("SECURITY-1186") + @Test + public void finalizer() throws Exception { + try { + evaluate(new GenericWhitelist(), "class Test { public void finalize() { } }; null"); + fail("Finalizers should be rejected"); + } catch (MultipleCompilationErrorsException e) { + assertThat(e.getErrorCollector().getErrorCount(), equalTo(1)); + Exception innerE = e.getErrorCollector().getException(0); + assertThat(innerE, instanceOf(SecurityException.class)); + assertThat(innerE.getMessage(), containsString("Object.finalize()")); + } + } + + @Test + public void alwaysRejectPermanentlyBlacklisted() throws Exception { + assertRejected(new StaticWhitelist("staticMethod java.lang.System exit int"), + "staticMethod java.lang.System exit int", + "System.exit(1)"); + assertRejected(new StaticWhitelist("method java.lang.Runtime exit int", "staticMethod java.lang.Runtime getRuntime"), + "method java.lang.Runtime exit int", + "Runtime r = Runtime.getRuntime();\n" + + "r.exit(1)"); + assertRejected(new StaticWhitelist("staticMethod java.lang.Runtime getRuntime", "method java.lang.Runtime halt int"), + "method java.lang.Runtime halt int", + "Runtime r = Runtime.getRuntime();\n" + + "r.halt(1)"); + } + +// @Issue("JENKINS-56682") + @Test + public void scriptInitializersAtFieldSyntax() throws Exception { + assertEvaluate(new GenericWhitelist(), 3, + "import groovy.transform.Field\n" + + "@Field static int foo = 1\n" + + "@Field int bar = foo + 1\n" + + "@Field int baz = bar + 1\n" + + "baz"); + } + +// @Issue("JENKINS-56682") + @Test + public void scriptInitializersClassSyntax() throws Exception { + assertEvaluate(new GenericWhitelist(), 2, + "class MyScript extends Script {\n" + + " { MyScript.foo++ }\n" + // The instance initializer seems to be context sensitive, if placed below the field it is treated as a closure... + " static { MyScript.foo++ }\n" + + " static int foo = 0\n" + + " def run() { MyScript.foo }\n" + + "}\n"); + } + +// @Issue("SECURITY-1538") + @Test public void blockMethodNameInMethodCalls() throws Exception { + assertRejected(new GenericWhitelist(), "staticMethod cd.go.contrib.plugins.configrepo.groovy.sandbox.StaticTestExample getInstance", + "import cd.go.contrib.plugins.configrepo.groovy.sandbox.StaticTestExample\n" + + "1.({ StaticTestExample.getInstance(); 'toString' }())()"); + } + +// @Issue("SECURITY-1538") + @Test public void blockPropertyNameInAssignment() throws Exception { + assertRejected(new GenericWhitelist(), "staticMethod cd.go.contrib.plugins.configrepo.groovy.sandbox.StaticTestExample getInstance", + "import cd.go.contrib.plugins.configrepo.groovy.sandbox.StaticTestExample\n" + + "class Test { def x = 0 }\n" + + "def t = new Test()\n" + + "t.({ StaticTestExample.getInstance(); 'x' }()) = 1\n"); + } + +// @Issue("SECURITY-1538") + @Test public void blockPropertyNameInPrefixPostfixExpressions() throws Exception { + assertRejected(new GenericWhitelist(), "staticMethod cd.go.contrib.plugins.configrepo.groovy.sandbox.StaticTestExample getInstance", + "import cd.go.contrib.plugins.configrepo.groovy.sandbox.StaticTestExample\n" + + "class Test { def x = 0 }\n" + + "def t = new Test()\n" + + "t.({ StaticTestExample.getInstance(); 'x' }())++\n"); + } + +// @Issue("SECURITY-1538") + @Test public void blockSubexpressionsInPrefixPostfixExpressions() throws Exception { + assertRejected(new GenericWhitelist(), "staticMethod cd.go.contrib.plugins.configrepo.groovy.sandbox.StaticTestExample getInstance", + "import cd.go.contrib.plugins.configrepo.groovy.sandbox.StaticTestExample\n" + + "++({ StaticTestExample.getInstance(); 1 }())\n"); + assertRejected(new GenericWhitelist(), "staticMethod cd.go.contrib.plugins.configrepo.groovy.sandbox.StaticTestExample getInstance", + "import cd.go.contrib.plugins.configrepo.groovy.sandbox.StaticTestExample\n" + + "({ StaticTestExample.getInstance(); 1 }())++\n"); + } + +// @Issue("SECURITY-1579") + @Test public void blockInitialExpressionsInConstructorsCallingSuper() throws Exception { + assertRejected(new GenericWhitelist(), "staticMethod cd.go.contrib.plugins.configrepo.groovy.sandbox.StaticTestExample getInstance", + "import cd.go.contrib.plugins.configrepo.groovy.sandbox.StaticTestExample\n" + + "class B {}\n" + + "class A extends B {\n" + + " A(x = StaticTestExample.getInstance()) {\n" + + " super()\n" + + " }\n" + + "}\n" + + "new A()\n"); + } + } diff --git a/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/StaticTestExample.java b/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/StaticTestExample.java new file mode 100644 index 00000000..bf1d7831 --- /dev/null +++ b/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/StaticTestExample.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 ThoughtWorks, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cd.go.contrib.plugins.configrepo.groovy.sandbox; + +public class StaticTestExample { + + public static Object getInstance() { + return new Object(); + } +} diff --git a/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/AnnotatedWhitelist.java b/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/AnnotatedWhitelist.java index 2a0759e8..79f1ab9c 100644 --- a/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/AnnotatedWhitelist.java +++ b/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/AnnotatedWhitelist.java @@ -24,6 +24,7 @@ package cd.go.contrib.plugins.configrepo.groovy.sandbox.whitelists; +import javax.annotation.Nonnull; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -46,7 +47,7 @@ private static final class Impl extends Whitelist { this.restricted = restricted; } - private boolean allowed(AccessibleObject o) { + private boolean allowed(@Nonnull AccessibleObject o) { Whitelisted ann = o.getAnnotation(Whitelisted.class); if (ann == null) { return false; diff --git a/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/GenericWhitelistTest.java b/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/GenericWhitelistTest.java index a2b8a0bf..2dc0a8b8 100644 --- a/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/GenericWhitelistTest.java +++ b/sandbox/src/test/java/cd/go/contrib/plugins/configrepo/groovy/sandbox/whitelists/GenericWhitelistTest.java @@ -37,6 +37,7 @@ public class GenericWhitelistTest { StaticWhitelistTest.sanity(GenericWhitelist.class.getResource("generic-whitelist")); } +// @Issue("SECURITY-538") @Test public void dynamicSubscript() throws Exception { String dangerous = Dangerous.class.getName(); Whitelist wl = new ProxyWhitelist(new GenericWhitelist(), new AnnotatedWhitelist());