Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.model.Item;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.util.FormValidation;

Expand Down Expand Up @@ -374,7 +375,8 @@ public Object evaluate(ClassLoader loader, Binding binding, @CheckForNull TaskLi
memoryProtectedLoader = new CleanGroovyClassLoader(loader);
loaderF.set(sh, memoryProtectedLoader);
}
return sh.evaluate(ScriptApproval.get().using(script, GroovyLanguage.get()));
Run run = (Run) binding.getVariable("build");
return sh.evaluate(ScriptApproval.get().using(script, GroovyLanguage.get(), run));
}

} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package org.jenkinsci.plugins.scriptsecurity.scripts;

import hudson.model.Run;
import jenkins.model.GlobalConfiguration;
import jenkins.model.GlobalConfigurationCategory;
import net.sf.json.JSONArray;
Expand Down Expand Up @@ -467,9 +468,32 @@ public synchronized String using(@NonNull String script, @NonNull Language langu
// Probably need not add to pendingScripts, since generally that would have happened already in configuring.
throw new UnapprovedUsageException(hash);
}

return script;
}
/**
* Called when a script is about to be used (evaluated).
* @param script a possibly unapproved script
* @param language the language in which it is written
* @param run the run executing the groovy script.
* @return {@code script}, for convenience
* @throws UnapprovedUsageException in case it has not yet been approved
*/
public synchronized String using(@NonNull String script, @NonNull Language language, @NonNull Run run) throws UnapprovedUsageException {
if (script.length() == 0) {
// As a special case, always consider the empty script preapproved, as this is usually the default for new fields,
// and in many cases there is some sensible behavior for an emoty script which we want to permit.
ScriptListener.fireScriptFromPipelineEvent(script, run);
return script;
}
String hash = hash(script, language.getName());
if (!approvedScriptHashes.contains(hash)) {
// Probably need not add to pendingScripts, since generally that would have happened already in configuring.
throw new UnapprovedUsageException(hash);
}
ScriptListener.fireScriptFromPipelineEvent(script, run);
return script;
}

// Only for testing
synchronized boolean isScriptHashApproved(String hash) {
return approvedScriptHashes.contains(hash);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.jenkinsci.plugins.scriptsecurity.scripts;

import hudson.ExtensionPoint;
import hudson.model.Run;
import jenkins.util.Listeners;

/**
* A listener to track usage of Groovy scripts running outside of a sandbox.
*
* @see org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript#evaluate(ClassLoader, groovy.lang.Binding, hudson.model.TaskListener)
*/
public interface ScriptListener extends ExtensionPoint {

/**
* Called when a groovy script is executed in a pipeline outside of a sandbox.
*
* @see org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript#evaluate(ClassLoader, groovy.lang.Binding, hudson.model.TaskListener)
* @param script The Groovy script that is excecuted.
* @param run The run calling the Groovy script.
*/
void onScriptFromPipeline(String script, Run run);

/**
* Fires the {@link #onScriptFromPipeline(String, Run)} event to track the usage of groovy scripts running outside the sandbox.
*
* @see org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript#evaluate(ClassLoader, groovy.lang.Binding, hudson.model.TaskListener)
* @param script The Groovy script that is excecuted.
* @param run The run calling the Groovy script.
*/
static void fireScriptFromPipelineEvent(String script, Run run) {
Listeners.notify(ScriptListener.class, true, listener -> listener.onScriptFromPipeline(script, run));
}
}