Skip to content

Commit ce72271

Browse files
authored
Merge pull request #205 from abayer/jenkins-33614
[FIXED JENKINS-33614] Add link to script approval when a rejection occurs
2 parents 7ac0d46 + d2d4104 commit ce72271

File tree

3 files changed

+110
-0
lines changed

3 files changed

+110
-0
lines changed

src/main/java/org/jenkinsci/plugins/workflow/cps/SandboxContinuable.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,27 @@
22

33
import com.cloudbees.groovy.cps.Continuable;
44
import com.cloudbees.groovy.cps.Outcome;
5+
6+
import java.io.IOException;
57
import java.util.List;
8+
9+
import hudson.Extension;
10+
import hudson.MarkupText;
11+
import hudson.console.ConsoleAnnotationDescriptor;
12+
import hudson.console.ConsoleAnnotator;
13+
import hudson.console.ConsoleNote;
14+
import jenkins.model.Jenkins;
15+
import org.jenkinsci.Symbol;
616
import org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException;
717
import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox;
818
import org.jenkinsci.plugins.scriptsecurity.scripts.ApprovalContext;
919
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
20+
import org.kohsuke.stapler.Stapler;
21+
import org.kohsuke.stapler.StaplerRequest;
1022

1123
import java.util.concurrent.Callable;
24+
import java.util.logging.Level;
25+
import java.util.logging.Logger;
1226
import javax.annotation.CheckForNull;
1327

1428
/**
@@ -36,6 +50,12 @@ public Outcome call() {
3650
RejectedAccessException x = findRejectedAccessException(outcome.getAbnormal());
3751
if (x != null) {
3852
ScriptApproval.get().accessRejected(x, ApprovalContext.create());
53+
try {
54+
e.getOwner().getListener().getLogger().println(x.getMessage() + ". " +
55+
ScriptApprovalNote.encodeTo(Messages.SandboxContinuable_ScriptApprovalLink()));
56+
} catch (IOException ex) {
57+
LOGGER.log(Level.WARNING, null, ex);
58+
}
3959
}
4060
return outcome;
4161
}
@@ -59,4 +79,52 @@ public Outcome call() {
5979
}
6080
}
6181

82+
public static final class ScriptApprovalNote extends ConsoleNote {
83+
private int length;
84+
85+
public ScriptApprovalNote(int length) {
86+
this.length = length;
87+
}
88+
89+
@Override
90+
public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) {
91+
if (Jenkins.getActiveInstance().hasPermission(Jenkins.RUN_SCRIPTS)) {
92+
String url = "/" + ScriptApproval.get().getUrlName();
93+
94+
StaplerRequest req = Stapler.getCurrentRequest();
95+
96+
if (req!=null) {
97+
// if we are serving HTTP request, we want to use app relative URL
98+
url = req.getContextPath()+url;
99+
} else {
100+
// otherwise presumably this is rendered for e-mails and other non-HTTP stuff
101+
url = Jenkins.getInstance().getRootUrl()+url.substring(1);
102+
}
103+
104+
text.addMarkup(charPos, charPos + length, "<a href='" + url + "'>", "</a>");
105+
}
106+
107+
return null;
108+
}
109+
110+
public static String encodeTo(String text) {
111+
try {
112+
return new ScriptApprovalNote(text.length()).encode()+text;
113+
} catch (IOException e) {
114+
// impossible, but don't make this a fatal problem
115+
LOGGER.log(Level.WARNING, "Failed to serialize "+ScriptApprovalNote.class,e);
116+
return text;
117+
}
118+
}
119+
120+
@Extension
121+
@Symbol("scriptApprovalLink")
122+
public static class DescriptorImpl extends ConsoleAnnotationDescriptor {
123+
public String getDisplayName() {
124+
return "Script Approval Link";
125+
}
126+
}
127+
}
128+
129+
private static final Logger LOGGER = Logger.getLogger(SandboxContinuable.class.getName());
62130
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
Snippetizer.this_step_should_not_normally_be_used_in=This step should not normally be used in your script. Consult the inline help for details.
2+
SandboxContinuable.ScriptApprovalLink=Administrators can decide whether to approve or reject this signature.

src/test/java/org/jenkinsci/plugins/workflow/cps/CpsFlowDefinition2Test.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,25 @@
2525
package org.jenkinsci.plugins.workflow.cps;
2626

2727
import com.cloudbees.groovy.cps.CpsTransformer;
28+
import com.gargoylesoftware.htmlunit.TextPage;
29+
import com.gargoylesoftware.htmlunit.html.DomNodeUtil;
30+
import com.gargoylesoftware.htmlunit.html.HtmlPage;
2831
import hudson.Functions;
2932
import hudson.model.Computer;
3033
import hudson.model.Executor;
34+
import hudson.model.Item;
3135
import hudson.model.Result;
36+
3237
import java.util.logging.Level;
38+
39+
import hudson.security.GlobalMatrixAuthorizationStrategy;
40+
import jenkins.model.Jenkins;
3341
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
3442
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
3543
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
3644
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
45+
46+
import static org.hamcrest.Matchers.containsString;
3747
import static org.junit.Assert.*;
3848

3949
import org.junit.Assert;
@@ -44,6 +54,7 @@
4454
import org.junit.Test;
4555
import org.jvnet.hudson.test.BuildWatcher;
4656
import org.jvnet.hudson.test.Issue;
57+
import org.jvnet.hudson.test.JenkinsRule;
4758
import org.jvnet.hudson.test.LoggerRule;
4859

4960
public class CpsFlowDefinition2Test extends AbstractCpsFlowTest {
@@ -169,6 +180,15 @@ public void superCallsSandboxed() throws Exception {
169180

170181
@Test
171182
public void sandboxInvokerUsed() throws Exception {
183+
jenkins.jenkins.setSecurityRealm(jenkins.createDummySecurityRealm());
184+
GlobalMatrixAuthorizationStrategy gmas = new GlobalMatrixAuthorizationStrategy();
185+
// Set up a user with RUN_SCRIPTS and one without..
186+
gmas.add(Jenkins.RUN_SCRIPTS, "runScriptsUser");
187+
gmas.add(Jenkins.READ, "runScriptsUser");
188+
gmas.add(Item.READ, "runScriptsUser");
189+
gmas.add(Jenkins.READ, "otherUser");
190+
gmas.add(Item.READ, "otherUser");
191+
jenkins.jenkins.setAuthorizationStrategy(gmas);
172192
WorkflowJob job = jenkins.jenkins.createProject(WorkflowJob.class, "p");
173193
job.setDefinition(new CpsFlowDefinition("[a: 1, b: 2].collectEntries { k, v ->\n" +
174194
" Jenkins.getInstance()\n" +
@@ -177,6 +197,27 @@ public void sandboxInvokerUsed() throws Exception {
177197

178198
WorkflowRun r = jenkins.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get());
179199
jenkins.assertLogContains("org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use staticMethod jenkins.model.Jenkins getInstance", r);
200+
jenkins.assertLogContains("Scripts not permitted to use staticMethod jenkins.model.Jenkins getInstance. " + Messages.SandboxContinuable_ScriptApprovalLink(), r);
201+
202+
JenkinsRule.WebClient wc = jenkins.createWebClient();
203+
204+
wc.login("runScriptsUser");
205+
// make sure we see the annotation for the RUN_SCRIPTS user.
206+
HtmlPage rsp = wc.getPage(r, "console");
207+
assertEquals(1, DomNodeUtil.selectNodes(rsp, "//A[@href='" + jenkins.contextPath + "/scriptApproval']").size());
208+
209+
// make sure raw console output doesn't include the garbage and has the right message.
210+
TextPage raw = (TextPage)wc.goTo(r.getUrl()+"consoleText","text/plain");
211+
assertThat(raw.getContent(), containsString(" getInstance. " + Messages.SandboxContinuable_ScriptApprovalLink()));
212+
213+
wc.login("otherUser");
214+
// make sure we don't see the link for the other user.
215+
HtmlPage rsp2 = wc.getPage(r, "console");
216+
assertEquals(0, DomNodeUtil.selectNodes(rsp2, "//A[@href='" + jenkins.contextPath + "/scriptApproval']").size());
217+
218+
// make sure raw console output doesn't include the garbage and has the right message.
219+
TextPage raw2 = (TextPage)wc.goTo(r.getUrl()+"consoleText","text/plain");
220+
assertThat(raw2.getContent(), containsString(" getInstance. " + Messages.SandboxContinuable_ScriptApprovalLink()));
180221
}
181222

182223
@Issue("SECURITY-551")

0 commit comments

Comments
 (0)