Navigation Menu

Skip to content

Commit

Permalink
Merge pull request #2 from jenkinsci/classpath
Browse files Browse the repository at this point in the history
[FIXED JENKINS-22834] Support custom classpaths.
  • Loading branch information
jglick committed Aug 19, 2014
2 parents ccd8c92 + 604c7ba commit 5bc00e8
Show file tree
Hide file tree
Showing 18 changed files with 1,516 additions and 19 deletions.
Expand Up @@ -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;
Expand All @@ -57,11 +64,17 @@ public final class SecureGroovyScript extends AbstractDescribableImpl<SecureGroo

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() {
Expand All @@ -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
*/
Expand All @@ -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;
}

Expand All @@ -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 {
Expand Down
Expand Up @@ -25,6 +25,7 @@
package org.jenkinsci.plugins.scriptsecurity.scripts;

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

/**
* Receives notifications on approval-related events.
Expand All @@ -37,6 +38,13 @@ public abstract class ApprovalListener implements ExtensionPoint {
*/
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.