Skip to content
Permalink
Browse files

Merge pull request #2 from jenkinsci/classpath

[FIXED JENKINS-22834] Support custom classpaths.
  • Loading branch information...
jglick committed Aug 19, 2014
2 parents ccd8c92 + 604c7ba commit 5bc00e8bc41ed0980315fe25e97d81c0e038fa27
Showing with 1,516 additions and 19 deletions.
  1. +37 −11 src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java
  2. +8 −0 src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ApprovalListener.java
  3. +121 −0 src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ClasspathEntry.java
  4. +332 −8 src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval.java
  5. +4 −0 src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalLink.java
  6. +56 −0 src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/UnapprovedClasspathException.java
  7. +3 −0 ...ain/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript/config.jelly
  8. +3 −0 ...ources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript/help-classpath.html
  9. +36 −0 src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ClasspathEntry/config.jelly
  10. +5 −0 src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ClasspathEntry/help-path.html
  11. +2 −0 src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/Messages.properties
  12. +122 −0 src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/index.jelly
  13. +618 −0 src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest.java
  14. +42 −0 src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ClasspathEntryTest.java
  15. +126 −0 src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest.java
  16. +1 −0 ...st/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest/README.md
  17. BIN ...i/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest/script-security-plugin-testjar.jar
  18. BIN ...s/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest/updated/script-security-plugin-testjar.jar
@@ -32,14 +32,21 @@
import hudson.model.Descriptor;
import hudson.model.Item;
import hudson.util.FormValidation;
import java.util.concurrent.Callable;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import org.codehaus.groovy.control.CompilationFailedException;
import org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException;
import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
import org.jenkinsci.plugins.scriptsecurity.scripts.ApprovalContext;
import org.jenkinsci.plugins.scriptsecurity.scripts.ClasspathEntry;
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
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.stapler.DataBoundConstructor;
@@ -57,11 +64,17 @@

private final String script;
private final boolean sandbox;
private final @CheckForNull List<ClasspathEntry> classpath;
private transient boolean calledConfiguring;

@DataBoundConstructor public SecureGroovyScript(String script, boolean sandbox) {
@DataBoundConstructor public SecureGroovyScript(String script, boolean sandbox, @CheckForNull List<ClasspathEntry> classpath) {
this.script = script;
this.sandbox = sandbox;
this.classpath = classpath;
}

@Deprecated public SecureGroovyScript(String script, boolean sandbox) {
this(script, sandbox, null);
}

private Object readResolve() {
@@ -77,9 +90,12 @@ public boolean isSandbox() {
return sandbox;
}

public @Nonnull List<ClasspathEntry> getClasspath() {
return classpath != null ? classpath : Collections.<ClasspathEntry>emptyList();
}

/**
* To be called in your own {@link DataBoundConstructor} when storing the field of this type.
* Should always be called, though it does nothing when {@link #isSandbox}.
* @param context an approval context
* @return this object
*/
@@ -88,6 +104,9 @@ public SecureGroovyScript configuring(ApprovalContext context) {
if (!sandbox) {
ScriptApproval.get().configuring(script, GroovyLanguage.get(), context);
}
for (ClasspathEntry entry : getClasspath()) {
ScriptApproval.get().configuring(entry, context);
}
return this;
}

@@ -99,35 +118,42 @@ public SecureGroovyScript configuring(ApprovalContext context) {
/** Convenience form of {@link #configuring} that calls {@link ApprovalContext#withCurrentUser} and {@link ApprovalContext#withItemAsKey}. */
public SecureGroovyScript configuringWithKeyItem() {
ApprovalContext context = ApprovalContext.create();
if (!sandbox) {
context = context.withCurrentUser().withItemAsKey(currentItem());
}
context = context.withCurrentUser().withItemAsKey(currentItem());
return configuring(context);
}

/** Convenience form of {@link #configuring} that calls {@link ApprovalContext#withCurrentUser} and {@link ApprovalContext#withItem}. */
public SecureGroovyScript configuringWithNonKeyItem() {
ApprovalContext context = ApprovalContext.create();
if (!sandbox) {
context = context.withCurrentUser().withItem(currentItem());
}
context = context.withCurrentUser().withItem(currentItem());
return configuring(context);
}

/**
* Runs the Groovy script, using the sandbox if so configured.
* @param loader a class loader for constructing the shell, such as {@link PluginManager#uberClassLoader};
* <strong>do not allow a user-customized classpath</strong>
* @param loader a class loader for constructing the shell, such as {@link PluginManager#uberClassLoader} (will be augmented by {@link #getClasspath} if nonempty)
* @param binding Groovy variable bindings
* @return the result of evaluating script using {@link GroovyShell#evaluate(String)}
* @throws Exception in case of a general problem
* @throws RejectedAccessException in case of a sandbox issue
* @throws UnapprovedUsageException in case of a non-sandbox issue
* @throws UnapprovedClasspathException in case some unapproved classpath entries were requested
*/
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");
}
List<ClasspathEntry> cp = getClasspath();
if (!cp.isEmpty()) {
List<URL> urlList = new ArrayList<URL>(cp.size());

for (ClasspathEntry entry : cp) {
ScriptApproval.get().using(entry);
urlList.add(entry.getURL());
}

loader = new URLClassLoader(urlList.toArray(new URL[urlList.size()]), loader);
}
if (sandbox) {
GroovyShell shell = new GroovyShell(loader, binding, GroovySandbox.createSecureCompilerConfiguration());
try {
@@ -25,6 +25,7 @@
package org.jenkinsci.plugins.scriptsecurity.scripts;

import hudson.ExtensionPoint;
import java.net.URL;

/**
* Receives notifications on approval-related events.
@@ -37,6 +38,13 @@
*/
public abstract void onApproved(String hash);

/**
* Called when a classpath entry is approved.
* @param hash an opaque token as in {@link UnapprovedClasspathException#getHash}
* @param url its location
*/
public void onApprovedClasspathEntry(String hash, URL url) {}

// TODO as needed: onDenied, onCleared

}
@@ -0,0 +1,121 @@
/*
* The MIT License
*
* Copyright (c) 2014 IKEDA Yasuyuki
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package org.jenkinsci.plugins.scriptsecurity.scripts;

import java.io.File;

import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;

import hudson.Extension;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.model.Items;
import hudson.util.FormValidation;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import javax.annotation.Nonnull;

/**
* A classpath entry used for a script.
*/
public final class ClasspathEntry extends AbstractDescribableImpl<ClasspathEntry> {

private final @Nonnull URL url;

@DataBoundConstructor
public ClasspathEntry(@Nonnull String path) throws MalformedURLException {
url = pathToURL(path);
}

static URL pathToURL(String path) throws MalformedURLException {
try {
return new URL(path);
} catch (MalformedURLException x) {
return new File(path).toURI().toURL();
}
}

static String urlToPath(URL url) {
if (url.getProtocol().equals("file")) {
try {
return new File(url.toURI()).getAbsolutePath();
} catch (URISyntaxException x) {
// ?
}
}
return url.toString();
}

public @Nonnull String getPath() {
return urlToPath(url);
}

public @Nonnull URL getURL() {
return url;
}

@Override
public String toString() {
return url.toString();
}

@Override
public boolean equals(Object obj) {
return obj instanceof ClasspathEntry && ((ClasspathEntry) obj).url.equals(url);
}

@Override public int hashCode() {
return url.hashCode();
}

@Extension
public static class DescriptorImpl extends Descriptor<ClasspathEntry> {
@Override
public String getDisplayName() {
return "ClasspathEntry";
}

public FormValidation doCheckPath(@QueryParameter String value) {
if (StringUtils.isBlank(value)) {
return FormValidation.warning("Enter a file path or URL."); // TODO I18N
}
try {
return ScriptApproval.get().checking(new ClasspathEntry(value));
} catch (MalformedURLException x) {
return FormValidation.error(x, "Could not parse: " + value); // TODO I18N
}
}
}

@Initializer(before=InitMilestone.EXTENSIONS_AUGMENTED) public static void alias() {
Items.XSTREAM2.alias("entry", ClasspathEntry.class);
}

}

0 comments on commit 5bc00e8

Please sign in to comment.
You can’t perform that action at this time.