Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FIXED JENKINS-26363 Input step should permit cancellation from anyon…
…e with Job.CANCEL permission]

Originally-Committed-As: 85c610870b3611c81c20d46de73670f0ab39a7be
  • Loading branch information
tfennelly committed Mar 4, 2015
1 parent 2646bb8 commit 107feb4
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 1 deletion.
Expand Up @@ -2,8 +2,16 @@

import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.model.BooleanParameterDefinition;
import hudson.model.Job;
import hudson.model.User;
import hudson.model.queue.QueueTaskFuture;

import java.util.Arrays;
import java.util.List;

import hudson.security.ACL;
import hudson.security.GlobalMatrixAuthorizationStrategy;
import jenkins.model.Jenkins;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
Expand All @@ -14,6 +22,7 @@
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;

/**
Expand Down Expand Up @@ -65,4 +74,77 @@ public void parameter() throws Exception {
System.out.println(b.getLog());
assertTrue(b.getLog().contains("after: false"));
}

@Test
@Issue("JENKINS-26363")
public void test_cancel_run_by_input() throws Exception {
JenkinsRule.WebClient webClient = j.createWebClient();
JenkinsRule.DummySecurityRealm dummySecurityRealm = j.createDummySecurityRealm();
GlobalMatrixAuthorizationStrategy authorizationStrategy = new GlobalMatrixAuthorizationStrategy();

j.jenkins.setSecurityRealm(dummySecurityRealm);

// Only give "alice" basic privs. That's normally not enough to Job.CANCEL, only for the fact that "alice"
// is listed as the submitter.
addUserWithPrivs("alice", authorizationStrategy);
// Only give "bob" basic privs. That's normally not enough to Job.CANCEL and "bob" is not the submitter,
// so they should be rejected.
addUserWithPrivs("bob", authorizationStrategy);
// Give "charlie" basic privs + Job.CANCEL. That should allow user3 cancel.
addUserWithPrivs("charlie", authorizationStrategy);
authorizationStrategy.add(Job.CANCEL, "charlie");

j.jenkins.setAuthorizationStrategy(authorizationStrategy);

final WorkflowJob foo = j.jenkins.createProject(WorkflowJob.class, "foo");
ACL.impersonate(User.get("alice").impersonate(), new Runnable() {
@Override
public void run() {
foo.setDefinition(new CpsFlowDefinition("input id: 'InputX', message: 'OK?', ok: 'Yes', submitter: 'alice'"));
}
});

runAndAbort(webClient, foo, "alice", true); // alice should work coz she's declared as 'submitter'
runAndAbort(webClient, foo, "bob", false); // bob shouldn't work coz he's not declared as 'submitter' and doesn't have Job.CANCEL privs
runAndAbort(webClient, foo, "charlie", true); // charlie should work coz he has Job.CANCEL privs
}

private void runAndAbort(JenkinsRule.WebClient webClient, WorkflowJob foo, String loginAs, boolean expectAbortOk) throws Exception {
// get the build going, and wait until workflow pauses
QueueTaskFuture<WorkflowRun> queueTaskFuture = foo.scheduleBuild2(0);
WorkflowRun run = queueTaskFuture.getStartCondition().get();
CpsFlowExecution execution = (CpsFlowExecution) run.getExecutionPromise().get();

while (run.getAction(InputAction.class) == null) {
execution.waitForSuspension();
}

webClient.login(loginAs);

InputAction inputAction = run.getAction(InputAction.class);
InputStepExecution is = inputAction.getExecution("InputX");
HtmlPage p = webClient.getPage(run, inputAction.getUrlName());

try {
j.submit(p.getFormByName(is.getId()), "abort");
assertEquals(0, inputAction.getExecutions().size());
queueTaskFuture.get();

List<String> log = run.getLog(1000);
System.out.println(log);
assertTrue(expectAbortOk);
assertEquals("Finished: ABORTED", log.get(log.size() - 1)); // Should be aborted
} catch (Exception e) {
List<String> log = run.getLog(1000);
System.out.println(log);
assertFalse(expectAbortOk);
assertEquals("Yes or Abort", log.get(log.size() - 1)); // Should still be paused at input
}
}

private void addUserWithPrivs(String username, GlobalMatrixAuthorizationStrategy authorizationStrategy) {
authorizationStrategy.add(Jenkins.READ, username);
authorizationStrategy.add(Jenkins.RUN_SCRIPTS, username);
authorizationStrategy.add(Job.READ, username);
}
}
Expand Up @@ -6,6 +6,7 @@
import hudson.console.HyperlinkNote;
import hudson.model.Failure;
import hudson.model.FileParameterValue;
import hudson.model.Job;
import hudson.model.ModelObject;
import hudson.model.ParameterDefinition;
import hudson.model.ParameterValue;
Expand Down Expand Up @@ -166,7 +167,7 @@ public HttpResponse doProceedEmpty() throws IOException {
*/
@RequirePOST
public HttpResponse doAbort() throws IOException, ServletException {
preSubmissionCheck();
preAbortCheck();

FlowInterruptedException e = new FlowInterruptedException(Result.ABORTED, new Rejection(User.current()));
outcome = new Outcome(null,e);
Expand All @@ -179,6 +180,17 @@ public HttpResponse doAbort() throws IOException, ServletException {
return HttpResponses.ok();
}

/**
* Check if the current user can abort/cancel the run from the input.
*/
private void preAbortCheck() {
if (isSettled()) {
throw new Failure("This input has been already given");
} if (!canCancel() && !input.canSubmit()) {
throw new Failure("You need to be '"+ input.getSubmitter() +"' (or have Job CANCEL permissions) to cancel this.");
}
}

/**
* Check if the current user can submit the input.
*/
Expand All @@ -199,6 +211,10 @@ private void postSettlement() throws IOException {
}
}

private boolean canCancel() {
return getRun().getParent().hasPermission(Job.CANCEL);
}

/**
* Parse the submitted {@link ParameterValue}s
*/
Expand Down

0 comments on commit 107feb4

Please sign in to comment.