diff --git a/pom.xml b/pom.xml
index 184ec2411..6e85a496b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,6 +21,7 @@
1.609.3
+ 1.15
@@ -62,6 +63,19 @@
github
1.14.0
+
+
+ org.jenkins-ci.plugins.workflow
+ workflow-aggregator
+ ${workflow.version}
+ test
+
+
+ org.jenkins-ci.plugins.workflow
+ workflow-multibranch
+ ${workflow.version}
+ test
+
diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java
index c573dbe33..e81dc2561 100644
--- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java
+++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java
@@ -272,7 +272,6 @@ private void doRetrieve(SCMHeadObserver observer, TaskListener listener, GHRepos
}
listener.getLogger().format("%n %d branches were processed%n", branches);
- if (repo.isPrivate()) {
listener.getLogger().format("%n Getting remote pull requests...%n");
int pullrequests = 0;
for (GHPullRequest ghPullRequest : repo.getPullRequests(GHIssueState.OPEN)) {
@@ -298,7 +297,14 @@ private void doRetrieve(SCMHeadObserver observer, TaskListener listener, GHRepos
continue;
}
}
- SCMRevision hash = new SCMRevisionImpl(head, ghPullRequest.getHead().getSha());
+ String trustedBase = trustedReplacement(repo, ghPullRequest);
+ SCMRevision hash;
+ if (trustedBase == null) {
+ hash = new SCMRevisionImpl(head, ghPullRequest.getHead().getSha());
+ } else {
+ listener.getLogger().format(" (not from a trusted source)%n");
+ hash = new UntrustedPullRequestSCMRevision(head, ghPullRequest.getHead().getSha(), trustedBase);
+ }
observer.observe(head, hash);
if (!observer.isObserving()) {
return;
@@ -306,9 +312,6 @@ private void doRetrieve(SCMHeadObserver observer, TaskListener listener, GHRepos
pullrequests++;
}
listener.getLogger().format("%n %d pull requests were processed%n", pullrequests);
- } else {
- listener.getLogger().format("%n Skipping pull requests for public repositories%n");
- }
}
@@ -375,12 +378,50 @@ protected SCMRevision doRetrieve(SCMHead head, TaskListener listener, GHReposito
if (head instanceof PullRequestSCMHead) {
int number = ((PullRequestSCMHead) head).getNumber();
ref = repo.getRef("pull/" + number + "/merge");
+ // getPullRequests makes an extra API call, but we need its current .base.sha
+ String trustedBase = trustedReplacement(repo, repo.getPullRequest(number));
+ if (trustedBase != null) {
+ return new UntrustedPullRequestSCMRevision(head, ref.getObject().getSha(), trustedBase);
+ }
} else {
ref = repo.getRef("heads/" + head.getName());
}
return new SCMRevisionImpl(head, ref.getObject().getSha());
}
+ @Override
+ public SCMRevision getTrustedRevision(SCMRevision revision, TaskListener listener) throws IOException, InterruptedException {
+ if (revision instanceof UntrustedPullRequestSCMRevision) {
+ PullRequestSCMHead head = (PullRequestSCMHead) revision.getHead();
+ UntrustedPullRequestSCMRevision rev = (UntrustedPullRequestSCMRevision) revision;
+ listener.getLogger().println("Loading trusted files from target branch at " + rev.baseHash + " rather than " + rev.getHash());
+ return new SCMRevisionImpl(head, rev.baseHash);
+ }
+ return revision;
+ }
+
+ /**
+ * Evaluates whether this pull request is coming from a trusted source.
+ * Quickest is to check whether the author of the PR
+ * is a collaborator of the repository.
+ * By checking all collaborators
+ * it is possible to further ascertain if they are in a team which was specifically granted push permission,
+ * but this is potentially expensive as there might be multiple pages of collaborators to retrieve.
+ * TODO since the GitHub API wrapper currently supports neither, we list all collaborator names and check for membership,
+ * paying the performance penalty without the benefit of the accuracy.
+ * @param ghPullRequest a PR
+ * @return the base revision, for an untrusted PR; null for a trusted PR
+ * @see PR metadata
+ * @see base revision oddity
+ */
+ private @CheckForNull String trustedReplacement(@Nonnull GHRepository repo, @Nonnull GHPullRequest ghPullRequest) throws IOException {
+ if (repo.getCollaboratorNames().contains(ghPullRequest.getUser().getLogin())) {
+ return null;
+ } else {
+ return ghPullRequest.getBase().getSha();
+ }
+ }
+
@Extension public static class DescriptorImpl extends SCMSourceDescriptor {
private static final Logger LOGGER = Logger.getLogger(DescriptorImpl.class.getName());
diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/UntrustedPullRequestSCMRevision.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/UntrustedPullRequestSCMRevision.java
new file mode 100644
index 000000000..ebb4d821c
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/UntrustedPullRequestSCMRevision.java
@@ -0,0 +1,52 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2016 CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.jenkinsci.plugins.github_branch_source;
+
+import jenkins.plugins.git.AbstractGitSCMSource;
+import jenkins.scm.api.SCMHead;
+
+/**
+ * Revision of a pull request which should load sensitive files from the base branch.
+ */
+class UntrustedPullRequestSCMRevision extends AbstractGitSCMSource.SCMRevisionImpl {
+
+ final String baseHash;
+
+ UntrustedPullRequestSCMRevision(SCMHead head, String hash, String baseHash) {
+ super(head, hash);
+ this.baseHash = baseHash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o) && baseHash.equals(((UntrustedPullRequestSCMRevision) o).baseHash);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode(); // good enough
+ }
+
+}