diff --git a/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/GroovySandbox.java b/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/GroovySandbox.java index 44334b922..39a836c52 100644 --- a/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/GroovySandbox.java +++ b/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/GroovySandbox.java @@ -72,7 +72,7 @@ public class GroovySandbox { public static @Nonnull ClassLoader createSecureClassLoader(ClassLoader base) { return new SandboxResolvingClassLoader(base); } - + /** * Runs a block in the sandbox. * You must have used {@link #createSecureCompilerConfiguration} to prepare the Groovy shell. @@ -132,10 +132,7 @@ public void run() { * @throws RejectedAccessException in case an attempted call was not whitelisted */ public static Object run(@Nonnull Script script, @Nonnull final Whitelist whitelist) throws RejectedAccessException { - Whitelist wrapperWhitelist = new ProxyWhitelist( - new ClassLoaderWhitelist(script.getClass().getClassLoader()), - whitelist); - GroovyInterceptor sandbox = new SandboxInterceptor(wrapperWhitelist); + GroovyInterceptor sandbox = createSandbox(script, whitelist); sandbox.register(); try { return script.run(); @@ -144,6 +141,20 @@ public static Object run(@Nonnull Script script, @Nonnull final Whitelist whitel } } + /** + * Prepares a sandbox for a script. + * You must have used {@link #createSecureCompilerConfiguration} to prepare the Groovy shell. + * @param script a script ready to {@link Script#run}, created for example by {@link GroovyShell#parse(String)} + * @param whitelist the whitelist to use, such as {@link Whitelist#all()} + * @return the sandbox for running this script + */ + public static GroovyInterceptor createSandbox(@Nonnull Script script, @Nonnull final Whitelist whitelist) { + Whitelist wrapperWhitelist = new ProxyWhitelist( + new ClassLoaderWhitelist(script.getClass().getClassLoader()), + whitelist); + return new SandboxInterceptor(wrapperWhitelist); + } + private GroovySandbox() {} } diff --git a/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java b/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java index db87d307b..610be843c 100644 --- a/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java +++ b/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java @@ -28,6 +28,7 @@ import groovy.lang.Binding; import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyShell; +import groovy.lang.Script; import hudson.Extension; import hudson.PluginManager; import hudson.model.AbstractDescribableImpl; @@ -36,6 +37,7 @@ import hudson.util.FormValidation; import java.beans.Introspector; +import java.io.IOException; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Field; @@ -68,6 +70,7 @@ import org.jenkinsci.plugins.scriptsecurity.scripts.UnapprovedClasspathException; import org.jenkinsci.plugins.scriptsecurity.scripts.UnapprovedUsageException; import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage; +import org.kohsuke.groovy.sandbox.GroovyInterceptor; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.Stapler; @@ -80,7 +83,7 @@ * Use {@code } to configure it from Jelly. */ public final class SecureGroovyScript extends AbstractDescribableImpl { - + private final @Nonnull String script; private final boolean sandbox; private final @CheckForNull List classpath; @@ -277,6 +280,10 @@ private static void cleanUpObjectStreamClassCaches(@Nonnull Class clazz) thro } } + public PreparedScript prepare(ClassLoader loader, Binding binding) throws IllegalAccessException, IOException { + return new PreparedScript(loader, binding); + } + /** * Runs the Groovy script, using the sandbox if so configured. * @param loader a class loader for constructing the shell, such as {@link PluginManager#uberClassLoader} (will be augmented by {@link #getClasspath} if nonempty) @@ -287,63 +294,99 @@ private static void cleanUpObjectStreamClassCaches(@Nonnull Class clazz) thro * @throws UnapprovedUsageException in case of a non-sandbox issue * @throws UnapprovedClasspathException in case some unapproved classpath entries were requested */ - @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Managed by GroovyShell.") public Object evaluate(ClassLoader loader, Binding binding) throws Exception { - if (!calledConfiguring) { - throw new IllegalStateException("you need to call configuring or a related method before using GroovyScript"); - } - URLClassLoader urlcl = null; - ClassLoader memoryProtectedLoader = null; - List cp = getClasspath(); - if (!cp.isEmpty()) { - List urlList = new ArrayList(cp.size()); - - for (ClasspathEntry entry : cp) { - ScriptApproval.get().using(entry); - urlList.add(entry.getURL()); - } - - loader = urlcl = new URLClassLoader(urlList.toArray(new URL[urlList.size()]), loader); - } - boolean canDoCleanup = false; + PreparedScript scriptInstance = prepare(loader, binding); try { - loader = GroovySandbox.createSecureClassLoader(loader); + return scriptInstance.run(); + } finally { + scriptInstance.cleanUp(); + } + } - Field loaderF = null; - try { - loaderF = GroovyShell.class.getDeclaredField("loader"); - loaderF.setAccessible(true); - canDoCleanup = true; - } catch (NoSuchFieldException nsme) { - LOGGER.log(Level.FINE, "GroovyShell fields have changed, field loader no longer exists -- memory leak fixes won't work"); + /** + * Allows access to execue a script multiple times without preparation and clean-up overhead. + * While the intricate logic to do this continues to be hidden from users. + */ + public final class PreparedScript { + + private final GroovyShell sh; + private final Script preparedScript; + private ClassLoader memoryProtectedLoader = null; + private GroovyInterceptor scriptSandbox = null; + private URLClassLoader urlcl = null; + private boolean canDoCleanup = false; + + /** + * @see @see SecureGroovyScript#evaluate() + */ + @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Managed by GroovyShell.") + private PreparedScript(ClassLoader loader, Binding binding) throws IllegalAccessException, IOException { + List cp = getClasspath(); + if (!cp.isEmpty()) { + List urlList = new ArrayList(cp.size()); + + for (ClasspathEntry entry : cp) { + ScriptApproval.get().using(entry); + urlList.add(entry.getURL()); + } + + loader = urlcl = new URLClassLoader(urlList.toArray(new URL[urlList.size()]), loader); } - GroovyShell sh; - if (sandbox) { - CompilerConfiguration cc = GroovySandbox.createSecureCompilerConfiguration(); - sh = new GroovyShell(loader, binding, cc); + try { + loader = GroovySandbox.createSecureClassLoader(loader); + Field loaderF = null; + try { + loaderF = GroovyShell.class.getDeclaredField("loader"); + loaderF.setAccessible(true); + canDoCleanup = true; + } catch (NoSuchFieldException nsme) { + LOGGER.log(Level.FINE, "GroovyShell fields have changed, field loader no longer exists -- memory leak fixes won't work"); + } - if (canDoCleanup) { - memoryProtectedLoader = new CleanGroovyClassLoader(loader, cc); - loaderF.set(sh, memoryProtectedLoader); + if (sandbox) { + CompilerConfiguration cc = GroovySandbox.createSecureCompilerConfiguration(); + sh = new GroovyShell(loader, binding, cc); + + if (canDoCleanup) { + memoryProtectedLoader = new CleanGroovyClassLoader(loader, cc); + loaderF.set(sh, memoryProtectedLoader); + } + + preparedScript = sh.parse(script); + scriptSandbox = GroovySandbox.createSandbox(preparedScript, Whitelist.all()); + } else { + sh = new GroovyShell(loader, binding); + if (canDoCleanup) { + memoryProtectedLoader = new CleanGroovyClassLoader(loader); + loaderF.set(sh, memoryProtectedLoader); + } + + preparedScript = sh.parse(ScriptApproval.get().using(script, GroovyLanguage.get())); } + } catch (Exception e) { + cleanUp(); + throw e; + } + } + public Object run() throws Exception { + if (sandbox) { + scriptSandbox.register(); try { - return GroovySandbox.run(sh.parse(script), Whitelist.all()); + return preparedScript.run(); } catch (RejectedAccessException x) { throw ScriptApproval.get().accessRejected(x, ApprovalContext.create()); + } finally { + scriptSandbox.unregister(); } } else { - sh = new GroovyShell(loader, binding); - if (canDoCleanup) { - memoryProtectedLoader = new CleanGroovyClassLoader(loader); - loaderF.set(sh, memoryProtectedLoader); - } - return sh.evaluate(ScriptApproval.get().using(script, GroovyLanguage.get())); + return preparedScript.run(); } + } - } finally { + public void cleanUp() throws IOException { try { if (canDoCleanup) { cleanUpLoader(memoryProtectedLoader, new HashSet(), new HashSet>()); diff --git a/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest.java b/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest.java index 9b1e1a515..363f896cb 100644 --- a/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest.java +++ b/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest.java @@ -77,7 +77,7 @@ public class SecureGroovyScriptTest { */ @Test public void basicApproval() throws Exception { r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); mockStrategy.grant(Jenkins.READ).everywhere().to("devel"); for (Permission p : Item.PERMISSIONS.getPermissions()) { @@ -142,7 +142,7 @@ public class SecureGroovyScriptTest { */ @Test public void testSandboxDefault_with_RUN_SCRIPTS_privs() throws Exception { r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); mockStrategy.grant(Jenkins.READ).everywhere().to("devel"); mockStrategy.grant(Jenkins.RUN_SCRIPTS).everywhere().to("devel"); @@ -151,7 +151,7 @@ public class SecureGroovyScriptTest { mockStrategy.grant(p).everywhere().to("devel"); } r.jenkins.setAuthorizationStrategy(mockStrategy); - + FreeStyleProject p = r.createFreeStyleProject("p"); JenkinsRule.WebClient wc = r.createWebClient(); wc.login("devel"); @@ -185,7 +185,7 @@ public class SecureGroovyScriptTest { */ @Test public void testSandboxDefault_without_RUN_SCRIPTS_privs() throws Exception { r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); mockStrategy.grant(Jenkins.READ).everywhere().to("devel"); for (Permission p : Item.PERMISSIONS.getPermissions()) { @@ -230,18 +230,18 @@ public class SecureGroovyScriptTest { private List getAllJarFiles() throws URISyntaxException { String testClassPath = String.format(StringUtils.join(getClass().getName().split("\\."), "/")); File testClassDir = new File(ClassLoader.getSystemResource(testClassPath).toURI()).getAbsoluteFile(); - + DirectoryScanner ds = new DirectoryScanner(); ds.setBasedir(testClassDir); ds.setIncludes(new String[]{ "*.jar" }); ds.scan(); - + List ret = new ArrayList(); - + for (String relpath: ds.getIncludedFiles()) { ret.add(new File(testClassDir, relpath)); } - + return ret; } @@ -267,20 +267,20 @@ private List files2entries(Iterable files) throws IOExcept private List getAllUpdatedJarFiles() throws URISyntaxException { String testClassPath = String.format(StringUtils.join(getClass().getName().split("\\."), "/")); File testClassDir = new File(ClassLoader.getSystemResource(testClassPath).toURI()).getAbsoluteFile(); - + File updatedDir = new File(testClassDir, "updated"); - + DirectoryScanner ds = new DirectoryScanner(); ds.setBasedir(updatedDir); ds.setIncludes(new String[]{ "*.jar" }); ds.scan(); - + List ret = new ArrayList(); - + for (String relpath: ds.getIncludedFiles()) { ret.add(new File(updatedDir, relpath)); } - + return ret; } @@ -303,7 +303,7 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { @Test public void testClasspathInSandbox() throws Exception { r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); mockStrategy.grant(Jenkins.READ).everywhere().to("devel"); for (Permission p : Item.PERMISSIONS.getPermissions()) { @@ -315,21 +315,21 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { for (File jarfile: getAllJarFiles()) { classpath.add(new ClasspathEntry(jarfile.getAbsolutePath())); } - + // Approve classpath. { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript("", true, classpath))); - + List pcps = ScriptApproval.get().getPendingClasspathEntries(); assertNotEquals(0, pcps.size()); for(ScriptApproval.PendingClasspathEntry pcp: pcps) { ScriptApproval.get().approveClasspathEntry(pcp.getHash()); } } - + final String testingDisplayName = "TESTDISPLAYNAME"; - + { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript( @@ -337,13 +337,13 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { true, classpath ))); - + FreeStyleBuild b = p.scheduleBuild2(0).get(); // fails for accessing non-whitelisted method. r.assertBuildStatus(Result.FAILURE, b); assertNotEquals(testingDisplayName, b.getDisplayName()); } - + { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript( @@ -354,13 +354,13 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { true, classpath ))); - + FreeStyleBuild b = p.scheduleBuild2(0).get(); // fails for accessing non-whitelisted method. r.assertBuildStatus(Result.FAILURE, b); assertNotEquals(testingDisplayName, b.getDisplayName()); } - + { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript( @@ -371,16 +371,16 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { true, classpath ))); - + FreeStyleBuild b = p.scheduleBuild2(0).get(); r.assertBuildStatusSuccess(b); assertEquals(testingDisplayName, b.getDisplayName()); } } - + @Test public void testNonapprovedClasspathInSandbox() throws Exception { r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); mockStrategy.grant(Jenkins.READ).everywhere().to("devel"); for (Permission p : Item.PERMISSIONS.getPermissions()) { @@ -392,81 +392,81 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { for (File jarfile: getAllJarFiles()) { String path = jarfile.getAbsolutePath(); classpath.add(new ClasspathEntry(path)); - + // String hash = ScriptApproval.hashClasspath(path); // ScriptApproval.get().addApprovedClasspathEntry(new ScriptApproval.ApprovedClasspathEntry(hash, path)); } - + String SCRIPT_TO_RUN = "\"Script is run\";"; - + // approve script { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript(SCRIPT_TO_RUN, false))); - + Set pss = ScriptApproval.get().getPendingScripts(); assertNotEquals(0, pss.size()); for(ScriptApproval.PendingScript ps: pss) { ScriptApproval.get().approveScript(ps.getHash()); } } - + // Success without classpaths { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript(SCRIPT_TO_RUN, false))); - + r.assertBuildStatusSuccess(p.scheduleBuild2(0).get()); } - + // Fail as the classpath is not approved. { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript(SCRIPT_TO_RUN, false, classpath))); - + r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); } - + // Fail even in sandbox. { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript(SCRIPT_TO_RUN, true, classpath))); - + r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); } - + // Approve classpath. { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript("", true, classpath))); - + List pcps = ScriptApproval.get().getPendingClasspathEntries(); assertNotEquals(0, pcps.size()); for(ScriptApproval.PendingClasspathEntry pcp: pcps) { ScriptApproval.get().approveClasspathEntry(pcp.getHash()); } } - + // Success without sandbox. { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript(SCRIPT_TO_RUN, false, classpath))); - + r.assertBuildStatusSuccess(p.scheduleBuild2(0)); } - + // Success also in sandbox. { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript(SCRIPT_TO_RUN, true, classpath))); - + r.assertBuildStatusSuccess(p.scheduleBuild2(0)); } } - + @Test public void testUpdatedClasspath() throws Exception { r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); mockStrategy.grant(Jenkins.READ).everywhere().to("devel"); for (Permission p : Item.PERMISSIONS.getPermissions()) { @@ -476,44 +476,44 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { // Copy jar files to temporary directory, then overwrite them with updated jar files. File tmpDir = tmpFolderRule.newFolder(); - + for (File jarfile: getAllJarFiles()) { FileUtils.copyFileToDirectory(jarfile, tmpDir); } - + List classpath = new ArrayList(); for (File jarfile: tmpDir.listFiles()) { classpath.add(new ClasspathEntry(jarfile.getAbsolutePath())); } - + String SCRIPT_TO_RUN = "\"Script is run\";"; - + // approve script { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript(SCRIPT_TO_RUN, false))); - + Set pss = ScriptApproval.get().getPendingScripts(); assertNotEquals(0, pss.size()); for(ScriptApproval.PendingScript ps: pss) { ScriptApproval.get().approveScript(ps.getHash()); } } - + // Success without classpaths { FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript(SCRIPT_TO_RUN, false))); - + r.assertBuildStatusSuccess(p.scheduleBuild2(0).get()); } - + FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript(SCRIPT_TO_RUN, false, classpath))); - + // Fail as the classpath is not approved. r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); - + // Approve classpath. { List pcps = ScriptApproval.get().getPendingClasspathEntries(); @@ -522,18 +522,18 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { ScriptApproval.get().approveClasspathEntry(pcp.getHash()); } } - + // Success as approved. r.assertBuildStatusSuccess(p.scheduleBuild2(0)); - + // overwrite jar files. for (File jarfile: getAllUpdatedJarFiles()) { FileUtils.copyFileToDirectory(jarfile, tmpDir); } - + // Fail as the updated jar files are not approved. r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); - + // Approve classpath. { List pcps = ScriptApproval.get().getPendingClasspathEntries(); @@ -542,14 +542,14 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { ScriptApproval.get().approveClasspathEntry(pcp.getHash()); } } - + // Success as approved. r.assertBuildStatusSuccess(p.scheduleBuild2(0)); } - + @Test public void testClasspathWithClassDirectory() throws Exception { r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); mockStrategy.grant(Jenkins.READ).everywhere().to("devel"); for (Permission p : Item.PERMISSIONS.getPermissions()) { @@ -559,19 +559,19 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { // Copy jar files to temporary directory, then overwrite them with updated jar files. File tmpDir = tmpFolderRule.newFolder(); - + for (File jarfile: getAllJarFiles()) { Expand e = new Expand(); e.setSrc(jarfile); e.setDest(tmpDir); e.execute(); } - + List classpath = new ArrayList(); classpath.add(new ClasspathEntry(tmpDir.getAbsolutePath())); - + final String testingDisplayName = "TESTDISPLAYNAME"; - + FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript( String.format( @@ -581,7 +581,7 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { true, classpath ))); - + // Fail as the classpath is not approved. { FreeStyleBuild b = p.scheduleBuild2(0).get(); @@ -590,17 +590,17 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { r.assertLogContains("is a class directory, which are not allowed", b); assertNotEquals(testingDisplayName, b.getDisplayName()); } - + // Unable to approve classpath. { List pcps = ScriptApproval.get().getPendingClasspathEntries(); assertEquals(0, pcps.size()); } } - + @Test public void testDifferentClasspathButSameContent() throws Exception { r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); mockStrategy.grant(Jenkins.READ).everywhere().to("devel"); for (Permission p : Item.PERMISSIONS.getPermissions()) { @@ -609,9 +609,9 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { r.jenkins.setAuthorizationStrategy(mockStrategy); final String testingDisplayName = "TESTDISPLAYNAME"; - + final List jars = getAllJarFiles(); - + FreeStyleProject p1 = r.createFreeStyleProject(); p1.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript( String.format( @@ -621,14 +621,14 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { true, files2entries(jars) ))); - + // Fail as the classpath is not approved. { FreeStyleBuild b = p1.scheduleBuild2(0).get(); r.assertBuildStatus(Result.FAILURE, b); assertNotEquals(testingDisplayName, b.getDisplayName()); } - + // Approve classpath. { List pcps = ScriptApproval.get().getPendingClasspathEntries(); @@ -637,14 +637,14 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { ScriptApproval.get().approveClasspathEntry(pcp.getHash()); } } - + // Success as approved. { FreeStyleBuild b = p1.scheduleBuild2(0).get(); r.assertBuildStatusSuccess(b); assertEquals(testingDisplayName, b.getDisplayName()); } - + // New job with jars in other places. FreeStyleProject p2 = r.createFreeStyleProject(); p2.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript( @@ -655,7 +655,7 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { true, files2entries(copy2TempDir(jars)) ))); - + // Success as approved. { FreeStyleBuild b = p2.scheduleBuild2(0).get(); @@ -663,10 +663,10 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { assertEquals(testingDisplayName, b.getDisplayName()); } } - + @Test public void testClasspathAutomaticApprove() throws Exception { r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); mockStrategy.grant(Jenkins.READ).everywhere().to("devel"); mockStrategy.grant(Jenkins.READ).everywhere().to("approver"); @@ -677,23 +677,23 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { mockStrategy.grant(p).everywhere().to("approver"); } r.jenkins.setAuthorizationStrategy(mockStrategy); - + JenkinsRule.WebClient wcDevel = r.createWebClient(); wcDevel.login("devel"); - + JenkinsRule.WebClient wcApprover = r.createWebClient(); wcApprover.login("approver"); - - + + List classpath = new ArrayList(); - + for (File jarfile: getAllJarFiles()) { classpath.add(new ClasspathEntry(jarfile.getAbsolutePath())); System.out.println(jarfile); } - + final String testingDisplayName = "TESTDISPLAYNAME"; - + FreeStyleProject p = r.createFreeStyleProject(); p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript( String.format( @@ -703,7 +703,7 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { true, classpath ))); - + // Deny classpath. { List pcps = ScriptApproval.get().getPendingClasspathEntries(); @@ -711,73 +711,73 @@ private List getAllUpdatedJarFiles() throws URISyntaxException { for(ScriptApproval.PendingClasspathEntry pcp: pcps) { ScriptApproval.get().denyClasspathEntry(pcp.getHash()); } - + assertEquals(0, ScriptApproval.get().getPendingClasspathEntries().size()); assertEquals(0, ScriptApproval.get().getApprovedClasspathEntries().size()); } - + // If configured by a user with RUN_SCRIPTS, the classpath is automatically approved { r.submit(wcApprover.getPage(p, "configure").getFormByName("config")); - + List pcps = ScriptApproval.get().getPendingClasspathEntries(); assertEquals(0, pcps.size()); List acps = ScriptApproval.get().getApprovedClasspathEntries(); assertNotEquals(0, acps.size()); - + for(ScriptApproval.ApprovedClasspathEntry acp: acps) { ScriptApproval.get().denyApprovedClasspathEntry(acp.getHash()); } - + assertEquals(0, ScriptApproval.get().getPendingClasspathEntries().size()); assertEquals(0, ScriptApproval.get().getApprovedClasspathEntries().size()); } - + // If configured by a user without RUN_SCRIPTS, approval is requested { r.submit(wcDevel.getPage(p, "configure").getFormByName("config")); - + List pcps = ScriptApproval.get().getPendingClasspathEntries(); assertNotEquals(0, pcps.size()); List acps = ScriptApproval.get().getApprovedClasspathEntries(); assertEquals(0, acps.size()); - + // don't remove pending classpaths. } - + // If configured by a user with RUN_SCRIPTS, the classpath is automatically approved, and removed from approval request. { assertNotEquals(0, ScriptApproval.get().getPendingClasspathEntries().size()); assertEquals(0, ScriptApproval.get().getApprovedClasspathEntries().size()); - + r.submit(wcApprover.getPage(p, "configure").getFormByName("config")); - + List pcps = ScriptApproval.get().getPendingClasspathEntries(); assertEquals(0, pcps.size()); List acps = ScriptApproval.get().getApprovedClasspathEntries(); assertNotEquals(0, acps.size()); - + for(ScriptApproval.ApprovedClasspathEntry acp: acps) { ScriptApproval.get().denyApprovedClasspathEntry(acp.getHash()); } - + assertEquals(0, ScriptApproval.get().getPendingClasspathEntries().size()); assertEquals(0, ScriptApproval.get().getApprovedClasspathEntries().size()); } - + // If run with SYSTEM user, an approval is requested. { r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); - + List pcps = ScriptApproval.get().getPendingClasspathEntries(); assertNotEquals(0, pcps.size()); List acps = ScriptApproval.get().getApprovedClasspathEntries(); assertEquals(0, acps.size()); - + for(ScriptApproval.PendingClasspathEntry pcp: pcps) { ScriptApproval.get().denyClasspathEntry(pcp.getHash()); } - + assertEquals(0, ScriptApproval.get().getPendingClasspathEntries().size()); assertEquals(0, ScriptApproval.get().getApprovedClasspathEntries().size()); } @@ -802,5 +802,40 @@ public void testSandboxClassResolution() throws Exception { assertTrue(e.getMessage().contains("staticMethod java.lang.System gc")); } } - + + @Test public void testSandboxUsesSelectedBinding() throws Exception { + SecureGroovyScript sgs = new SecureGroovyScript("return a++", true, null); + Binding b = new Binding(); + b.setVariable("a", 5); + SecureGroovyScript.PreparedScript script = sgs.configuringWithKeyItem().prepare(Jenkins.getInstance().getPluginManager().uberClassLoader, b); + try { + Object res = script.run(); + assertTrue((int) res == 5); + assertTrue((int) b.getVariable("a") == 6); + res = script.run(); + assertTrue((int) res == 6); + assertTrue((int) b.getVariable("a") == 7); + } catch (Exception e) { + script.cleanUp(); + throw e; + } + } + + @Test public void testNonSandboxUsesSelectedBinding() throws Exception { + SecureGroovyScript sgs = new SecureGroovyScript("return a++", false, null); + Binding b = new Binding(); + b.setVariable("a", 5); + SecureGroovyScript.PreparedScript script = sgs.configuringWithKeyItem().prepare(Jenkins.getInstance().getPluginManager().uberClassLoader, b); + try { + Object res = script.run(); + assertTrue((int) res == 5); + assertTrue((int) b.getVariable("a") == 6); + res = script.run(); + assertTrue((int) res == 6); + assertTrue((int) b.getVariable("a") == 7); + } catch (Exception e) { + script.cleanUp(); + throw e; + } + } }