package hudson.matrix;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import hudson.Extension;
import hudson.Util;
import hudson.matrix.MatrixBuild.MatrixBuildExecution;
import hudson.model.ParameterValue;
import hudson.model.ParametersAction;
import java.io.IOException;
import java.util.Map;
import static java.lang.Boolean.*;
import org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException;
import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.ProxyWhitelist;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.StaticWhitelist;
import org.jenkinsci.plugins.scriptsecurity.scripts.ApprovalContext;
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
/**
* Groovy filter script that accepts or rejects matrix {@link Combination}.
*
* Instances of this class is thread unsafe.
*
* @author Kohsuke Kawaguchi
*/
class FilterScript {
private final Script script;
FilterScript(Script script) {
this.script = script;
}
/**
* @param context
* Variables the script will see.
*/
private boolean evaluate(Binding context) {
script.setBinding(context);
try {
return TRUE.equals(GroovySandbox.run(script, Whitelist.all()));
} catch (RejectedAccessException x) {
throw ScriptApproval.get().accessRejected(x, ApprovalContext.create());
}
}
/**
* Obtains a number N such that "N%M==0" would create
* a reasonable sparse matrix for integer M.
*
* <p>
* This is bit like {@link Combination#toIndex(AxisList)}, but instead
* of creating a continuous number (which often maps different
* values of the same axis to the same index in modulo N residue ring,
* we use a prime number P as the base. I think this guarantees the uniform
* distribution in any N smaller than 2P (but proof, anyone?)
*/
private long toModuloIndex(AxisList axis, Combination c) {
long r = 0;
for (Axis a : axis) {
r += a.indexOf(c.get(a));
r *= 31;
}
return r;
}
/**
* Applies the filter to the specified combination in the context of {@code context}.
*/
public final boolean apply(MatrixBuildExecution context, Combination combination) {
return apply(context.getProject().getAxes(), combination, getConfiguredBinding(context));
}
/*package*/ boolean apply(AxisList axes, Combination c, Binding binding) {
for (Map.Entry<String, String> e : c.entrySet())
binding.setVariable(e.getKey(),e.getValue());
binding.setVariable("index",toModuloIndex(axes,c));
binding.setVariable("uniqueId", c.toIndex(axes));
return evaluate(binding);
}
private Binding getConfiguredBinding(final MatrixBuildExecution execution) {
final Binding binding = new Binding();
final ParametersAction parameters = execution.getBuild().getAction(ParametersAction.class);
if (parameters == null) return binding;
for (final ParameterValue pv: parameters) {
if (pv == null) continue;
final String name = pv.getName();
final String value = pv.createVariableResolver(execution.getBuild()).resolve(name);
binding.setVariable(name, value);
}
return binding;
}
public static FilterScript parse(String expression) {
return parse(expression, ACCEPT_ALL);
}
/**
* @since 1.541
*/
public static FilterScript parse(String expression, FilterScript defaultScript) {
if (Util.fixEmptyAndTrim(expression)==null)
return defaultScript;
GroovyShell shell = new GroovyShell(GroovySandbox.createSecureClassLoader(FilterScript.class.getClassLoader()), new Binding(), GroovySandbox.createSecureCompilerConfiguration());
return new FilterScript(shell.parse(expression));
}
/**
* Constant that always applies to any combination.
* @since 1.541
*/
/*package*/ static final FilterScript ACCEPT_ALL = new FilterScript(null) {
@Override
public boolean apply(AxisList axes, Combination combination, Binding binding) {
return true;
}
};
/**
* Constant that does not apply to any combination.
* @since 1.541
*/
/*package*/ static final FilterScript REJECT_ALL = new FilterScript(null) {
@Override
public boolean apply(AxisList axes, Combination combination, Binding binding) {
return false;
}
};
// TODO JENKINS-25804: harmless generic methods like this should be whitelisted in script-security
@Extension public static class ImpliesWhitelist extends ProxyWhitelist {
public ImpliesWhitelist() throws IOException {
super(new StaticWhitelist("staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods implies java.lang.Boolean java.lang.Boolean"));
}
}
}