Skip to content

Commit 6e29f5f

Browse files
committed
Add script listener to track usage
1 parent 0e7c803 commit 6e29f5f

File tree

3 files changed

+64
-11
lines changed

3 files changed

+64
-11
lines changed

src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,7 @@
3030
import groovy.lang.GroovyShell;
3131
import hudson.Extension;
3232
import hudson.PluginManager;
33-
import hudson.model.AbstractDescribableImpl;
34-
import hudson.model.Descriptor;
35-
import hudson.model.Item;
36-
import hudson.model.TaskListener;
33+
import hudson.model.*;
3734
import hudson.util.FormValidation;
3835

3936
import java.beans.Introspector;
@@ -62,11 +59,7 @@
6259
import org.codehaus.groovy.control.CompilerConfiguration;
6360
import org.codehaus.groovy.control.SourceUnit;
6461
import org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException;
65-
import org.jenkinsci.plugins.scriptsecurity.scripts.ApprovalContext;
66-
import org.jenkinsci.plugins.scriptsecurity.scripts.ClasspathEntry;
67-
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
68-
import org.jenkinsci.plugins.scriptsecurity.scripts.UnapprovedClasspathException;
69-
import org.jenkinsci.plugins.scriptsecurity.scripts.UnapprovedUsageException;
62+
import org.jenkinsci.plugins.scriptsecurity.scripts.*;
7063
import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage;
7164
import org.kohsuke.stapler.DataBoundConstructor;
7265
import org.kohsuke.stapler.QueryParameter;
@@ -374,7 +367,8 @@ public Object evaluate(ClassLoader loader, Binding binding, @CheckForNull TaskLi
374367
memoryProtectedLoader = new CleanGroovyClassLoader(loader);
375368
loaderF.set(sh, memoryProtectedLoader);
376369
}
377-
return sh.evaluate(ScriptApproval.get().using(script, GroovyLanguage.get()));
370+
Run run = (Run) binding.getVariable("build");
371+
return sh.evaluate(ScriptApproval.get().using(script, GroovyLanguage.get(), run));
378372
}
379373

380374
} finally {

src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
package org.jenkinsci.plugins.scriptsecurity.scripts;
2626

27+
import hudson.model.Run;
2728
import jenkins.model.GlobalConfiguration;
2829
import jenkins.model.GlobalConfigurationCategory;
2930
import net.sf.json.JSONArray;
@@ -467,9 +468,32 @@ public synchronized String using(@NonNull String script, @NonNull Language langu
467468
// Probably need not add to pendingScripts, since generally that would have happened already in configuring.
468469
throw new UnapprovedUsageException(hash);
469470
}
471+
472+
return script;
473+
}
474+
/**
475+
* Called when a script is about to be used (evaluated).
476+
* @param script a possibly unapproved script
477+
* @param language the language in which it is written
478+
* @param run the run executing the groovy script.
479+
* @return {@code script}, for convenience
480+
* @throws UnapprovedUsageException in case it has not yet been approved
481+
*/
482+
public synchronized String using(@NonNull String script, @NonNull Language language, @NonNull Run run) throws UnapprovedUsageException {
483+
if (script.length() == 0) {
484+
// As a special case, always consider the empty script preapproved, as this is usually the default for new fields,
485+
// and in many cases there is some sensible behavior for an emoty script which we want to permit.
486+
ScriptListener.fireScriptFromConsoleEvent(script, run);
487+
return script;
488+
}
489+
String hash = hash(script, language.getName());
490+
if (!approvedScriptHashes.contains(hash)) {
491+
// Probably need not add to pendingScripts, since generally that would have happened already in configuring.
492+
throw new UnapprovedUsageException(hash);
493+
}
494+
ScriptListener.fireScriptFromConsoleEvent(script, run);
470495
return script;
471496
}
472-
473497
// Only for testing
474498
synchronized boolean isScriptHashApproved(String hash) {
475499
return approvedScriptHashes.contains(hash);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.jenkinsci.plugins.scriptsecurity.scripts;
2+
3+
import hudson.ExtensionPoint;
4+
import hudson.model.Run;
5+
import jenkins.model.Jenkins;
6+
import jenkins.util.Listeners;
7+
8+
/**
9+
* A listener to track usage of Groovy scripts running outside of a sandbox.
10+
*
11+
* @see org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript#evaluate(groovy.lang.GroovyClassLoader, groovy.lang.Binding, hudson.model.TaskListener)
12+
*/
13+
public interface ScriptListener extends ExtensionPoint {
14+
15+
/**
16+
* Called when a groovy script is executed in a pipeline outside of a sandbox.
17+
*
18+
* @see org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript#evaluate(groovy.lang.GroovyClassLoader, groovy.lang.Binding, hudson.model.TaskListener)
19+
* @param script The Groovy script that is excecuted.
20+
* @param run The run calling the Groovy script.
21+
*/
22+
void onScriptFromPipeline(String script, Run run);
23+
24+
25+
/**
26+
* Fires the {@link #onScriptFromPipeline(String, Run)} event to track the usage of the script console.
27+
*
28+
* @see org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript#evaluate(ClassLoader, Binding, TaskListener)
29+
* @param script The Groovy script that is excecuted.
30+
* @param run The run calling the Groovy script.
31+
*/
32+
static void fireScriptFromConsoleEvent(String script, Run run) {
33+
Listeners.notify(ScriptListener.class, true, listener -> listener.onScriptFromPipeline(script, run));
34+
}
35+
}

0 commit comments

Comments
 (0)