Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added GitHub signature checking feature + tests #239

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c4f2f78
Checking "X-Hub-Signature" on GitHub hook POST reception. Added test …
Mar 11, 2015
dd33f21
FIX: Moved signature checking to GhprbRepository. Added tests
Mar 18, 2015
e36d52c
FIX: added UTF-8 encoding to project reporting in pom.xml
Apr 1, 2015
cc899f3
FIX: pulled latests changes from master
May 20, 2015
4cb28e3
Merge branch 'master' of https://github.com/jenkinsci/ghprb-plugin in…
May 20, 2015
a303155
Merge pull request #280 from jenkinsci/master
DavidTanner Jun 3, 2015
67cdc1c
Merge branch 'master' of https://github.com/jenkinsci/ghprb-plugin in…
Jun 8, 2015
d7b8b44
Correct typo 'conext' -> 'context'
rneatherway Jun 9, 2015
27e9361
Merge pull request #282 from rneatherway/patch-1
DavidTanner Jun 9, 2015
bfc99de
Merge pull request #101 from janinko/master
DavidTanner Jun 12, 2015
babfffb
Merge pull request #100 from DavidTanner/github-auth
DavidTanner Jun 12, 2015
b6b3766
[maven-release-plugin] prepare release ghprb-1.23
DavidTanner Jun 12, 2015
2ffd677
[maven-release-plugin] prepare for next development iteration
DavidTanner Jun 12, 2015
444e7e8
Change to just use the host name for the default domain
DavidTanner Jun 13, 2015
d72a4cb
Fix bug in groovy config
DavidTanner Jun 14, 2015
0df34d1
Merge pull request #106 from DavidTanner/master
DavidTanner Jun 14, 2015
30a06d6
[maven-release-plugin] prepare release ghprb-1.23.1
DavidTanner Jun 14, 2015
2083f43
[maven-release-plugin] prepare for next development iteration
DavidTanner Jun 14, 2015
d262961
config was still broken.
DavidTanner Jun 14, 2015
fb07915
Merge pull request #107 from DavidTanner/master
DavidTanner Jun 14, 2015
af6bb66
[maven-release-plugin] prepare release ghprb-1.23.2
DavidTanner Jun 14, 2015
f43d5f9
[maven-release-plugin] prepare for next development iteration
DavidTanner Jun 14, 2015
c3b851b
Merge branch 'master' of https://github.com/jenkinsci/ghprb-plugin in…
Jun 15, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<artifactId>ghprb</artifactId>
<name>GitHub Pull Request Builder</name>
<version>1.23-SNAPSHOT</version>
<version>1.24-SNAPSHOT</version>
<packaging>hpi</packaging>

<url>https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin</url>
Expand Down
4 changes: 1 addition & 3 deletions src/main/java/org/jenkinsci/plugins/ghprb/Ghprb.java
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,7 @@ private static String createCredentials(String serverAPIUrl, StandardCredentials
specifications.add(new SchemeSpecification(serverUri.getScheme()));
specifications.add(new PathSpecification(serverUri.getPath(), null, false));



Domain domain = new Domain(serverAPIUrl, "Auto generated credentials domain", specifications);
Domain domain = new Domain(serverUri.getHost(), "Auto generated credentials domain", specifications);
CredentialsStore provider = new SystemCredentialsProvider.StoreImpl();
provider.addDomain(domain, credentials);
return credentials.getId();
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/org/jenkinsci/plugins/ghprb/GhprbRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import hudson.model.TaskListener;
import jenkins.model.Jenkins;

import org.apache.commons.codec.binary.Hex;
import org.jenkinsci.plugins.ghprb.extensions.GhprbCommentAppender;
import org.jenkinsci.plugins.ghprb.extensions.GhprbCommitStatusException;
import org.jenkinsci.plugins.ghprb.extensions.GhprbExtension;
Expand All @@ -14,6 +15,9 @@
import org.kohsuke.github.GHEventPayload.IssueComment;
import org.kohsuke.github.GHEventPayload.PullRequest;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
Expand Down Expand Up @@ -302,6 +306,37 @@ void onPullRequestHook(PullRequest pr) {
GhprbTrigger.getDscp().save();
}

boolean checkSignature(String body, String signature) {
String secret = helper.getTrigger().getSecret();
if (secret != null && ! secret.isEmpty()) {
if (signature != null && signature.startsWith("sha1=")) {
String expected = signature.substring(5);
String algorithm = "HmacSHA1";
try {
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(), algorithm);
Mac mac = Mac.getInstance(algorithm);
mac.init(keySpec);
byte[] localSignatureBytes = mac.doFinal(body.getBytes("UTF-8"));
String localSignature = Hex.encodeHexString(localSignatureBytes);
if (! localSignature.equals(expected)) {
logger.log(Level.SEVERE, "Local signature {0} does not match external signature {1}",
new Object[] {localSignature, expected});
return false;
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Couldn't match both signatures");
return false;
}
} else {
logger.log(Level.SEVERE, "Request doesn't contain a signature. Check that github has a secret that should be attached to the hook");
return false;
}
}

logger.log(Level.INFO, "Signatures checking OK");
return true;
}

@VisibleForTesting
void setHelper(Ghprb helper) {
this.helper = helper;
Expand Down
42 changes: 32 additions & 10 deletions src/main/java/org/jenkinsci/plugins/ghprb/GhprbRootAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
Expand Down Expand Up @@ -47,27 +50,31 @@ public String getUrlName() {

public void doIndex(StaplerRequest req, StaplerResponse resp) {
String event = req.getHeader("X-GitHub-Event");
String signature = req.getHeader("X-Hub-Signature");
String type = req.getContentType();
String payload = null;
String body = null;

if ("application/json".equals(type)) {
BufferedReader br = null;
try {
br = req.getReader();
payload = IOUtils.toString(req.getReader());
} catch (IOException e) {
body = extractRequestBody(req);
if (body == null) {
logger.log(Level.SEVERE, "Can't get request body for application/json.");
return;
} finally {
IOUtils.closeQuietly(br);
}
payload = body;
} else if ("application/x-www-form-urlencoded".equals(type)) {
payload = req.getParameter("payload");
if (payload == null) {
body = extractRequestBody(req);
if (body == null || body.length() <= 8) {
logger.log(Level.SEVERE, "Request doesn't contain payload. "
+ "You're sending url encoded request, so you should pass github payload through 'payload' request parameter");
return;
}
try {
payload = URLDecoder.decode(body.substring(8), req.getCharacterEncoding());
} catch (UnsupportedEncodingException e) {
logger.log(Level.SEVERE, "Error while trying to encode the payload");
return;
}
}

if (payload == null) {
Expand All @@ -79,13 +86,28 @@ public void doIndex(StaplerRequest req, StaplerResponse resp) {

for (GhprbWebHook webHook : getWebHooks()) {
try {
webHook.handleWebHook(event, payload);
webHook.handleWebHook(event, payload, body, signature);
} catch (Exception e) {
logger.log(Level.SEVERE, "Unable to process web hook for: " + webHook.getProjectName(), e);
}
}

}

private String extractRequestBody(StaplerRequest req) {
String body = null;
BufferedReader br = null;
try {
br = req.getReader();
body = IOUtils.toString(br);
} catch (IOException e) {
body = null;
} finally {
IOUtils.closeQuietly(br);
}
return body;
}


private Set<GhprbWebHook> getWebHooks() {
final Set<GhprbWebHook> webHooks = new HashSet<GhprbWebHook>();
Expand Down
37 changes: 20 additions & 17 deletions src/main/java/org/jenkinsci/plugins/ghprb/GhprbTrigger.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,11 @@ public class GhprbTrigger extends GhprbTriggerBackwardsCompatible {
private List<GhprbBranch> whiteListTargetBranches;
private transient Ghprb helper;
private String project;
private String secret;
private GhprbGitHubAuth gitHubApiAuth;


private DescribableList<GhprbExtension, GhprbExtensionDescriptor> extensions;
private DescribableList<GhprbExtension, GhprbExtensionDescriptor> extensions = new DescribableList<GhprbExtension, GhprbExtensionDescriptor>(Saveable.NOOP);

public DescribableList<GhprbExtension, GhprbExtensionDescriptor> getExtensions() {
if (extensions == null) {
Expand All @@ -94,23 +95,25 @@ private void setExtensions(List<GhprbExtension> extensions) {
}

@DataBoundConstructor
public GhprbTrigger(String adminlist,
String whitelist,
String orgslist,
String cron,
String triggerPhrase,

public GhprbTrigger(String adminlist,
String whitelist,
String orgslist,
String cron,
String triggerPhrase,
Boolean onlyTriggerPhrase,
Boolean useGitHubHooks,
Boolean useGitHubHooks,
Boolean permitAll,
Boolean autoCloseFailedPullRequests,
Boolean displayBuildErrorsOnDownstreamBuilds,
Boolean autoCloseFailedPullRequests,
Boolean displayBuildErrorsOnDownstreamBuilds,
String commentFilePath,
List<GhprbBranch> whiteListTargetBranches,
Boolean allowMembersOfWhitelistedOrgsAsAdmin,
String msgSuccess,
String msgFailure,
String commitStatusContext,
String gitHubAuthId,
String secret,
List<GhprbExtension> extensions
) throws ANTLRException {
super(cron);
Expand All @@ -127,6 +130,7 @@ public GhprbTrigger(String adminlist,
this.whiteListTargetBranches = whiteListTargetBranches;
this.gitHubApiAuth = getDescriptor().getGitHubAuth(gitHubAuthId);
this.allowMembersOfWhitelistedOrgsAsAdmin = allowMembersOfWhitelistedOrgsAsAdmin;
this.secret = secret;
setExtensions(extensions);
configVersion = 1;
}
Expand Down Expand Up @@ -357,6 +361,10 @@ public String getCron() {
return cron;
}


public String getSecret() {
return secret;
}
public String getProject() {
return project;
}
Expand Down Expand Up @@ -482,7 +490,7 @@ public GhprbGitHubAuth getGitHubAuth(String gitHubAuthId) {
public List<GhprbGitHubAuth> getGithubAuth() {
if (githubAuth == null || githubAuth.size() == 0) {
githubAuth = new ArrayList<GhprbGitHubAuth>(1);
githubAuth.add(new GhprbGitHubAuth(null, null, "Blank description", null));
githubAuth.add(new GhprbGitHubAuth(null, null, "Anonymous connection", null));
}
return githubAuth;
}
Expand All @@ -491,16 +499,11 @@ public List<GhprbGitHubAuth> getDefaultAuth(List<GhprbGitHubAuth> githubAuth) {
if (githubAuth != null && githubAuth.size() > 0) {
return githubAuth;
}
List<GhprbGitHubAuth> defaults = new ArrayList<GhprbGitHubAuth>(1);
defaults.add(new GhprbGitHubAuth(null, null, "Blank description", null));
return defaults;
return getGithubAuth();
}



private String adminlist;


private String requestForTestingPhrase;

// map of jobs (by their fullName) and their map of pull requests
Expand Down Expand Up @@ -655,7 +658,7 @@ public boolean isUseDetailedComments() {

public ListBoxModel doFillGitHubAuthIdItems(@QueryParameter("gitHubAuth") String gitHubAuthId) {
ListBoxModel model = new ListBoxModel();
for (GhprbGitHubAuth auth : githubAuth) {
for (GhprbGitHubAuth auth : getGithubAuth()) {
String description = Util.fixNull(auth.getDescription());
int length = description.length();
length = length > 50 ? 50 : length;
Expand Down
8 changes: 5 additions & 3 deletions src/main/java/org/jenkinsci/plugins/ghprb/GhprbWebHook.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public GhprbWebHook(GhprbTrigger trigger) {
this.trigger = trigger;
}

public void handleWebHook(String event, String payload) {
public void handleWebHook(String event, String payload, String body, String signature) {

GhprbRepository repo = trigger.getRepository();

Expand All @@ -41,7 +41,8 @@ public void handleWebHook(String event, String payload) {
logger.log(Level.INFO, "Checking issue comment ''{0}'' for repo {1}",
new Object[] { issueComment.getComment(), repo.getName() }
);
repo.onIssueCommentHook(issueComment);
if (repo.checkSignature(body, signature))
repo.onIssueCommentHook(issueComment);
}

} else if ("pull_request".equals(event)) {
Expand All @@ -50,7 +51,8 @@ public void handleWebHook(String event, String payload) {
GHEventPayload.PullRequest.class);
if (matchRepo(repo, pr.getPullRequest().getRepository())) {
logger.log(Level.INFO, "Checking PR #{1} for {0}", new Object[] { repo.getName(), pr.getNumber() });
repo.onPullRequestHook(pr);
if (repo.checkSignature(body, signature))
repo.onPullRequestHook(pr);
}

} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ private void createCommitStatus(AbstractBuild<?, ?> build, TaskListener listener

listener.getLogger().println(String.format("Setting status of %s to %s with url %s and message: '%s'", sha1, state, url, message));
if (context != null) {
listener.getLogger().println(String.format("Using conext: " + context));
listener.getLogger().println(String.format("Using context: " + context));
}
try {
repo.createCommitStatus(sha1, state, url, message, context);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// Namespaces
xml = namespace("http://www.w3.org/XML/1998/namespace")
j = namespace("jelly:core")
f = namespace("/lib/form")
Expand Down Expand Up @@ -61,6 +60,7 @@ f.advanced() {
}
f.advanced(title: _("Trigger Setup")) {
f.entry(title: _("Trigger Setup")) {
f.hetero_list(items: instance.extensions, name: "extensions", oneEach: "true", hasHeader: "true", descriptors: descriptor.getExtensionDescriptors())
f.hetero_list(items: instance == null ? null : instance.extensions,
name: "extensions", oneEach: "true", hasHeader: "true", descriptors: descriptor.getExtensionDescriptors())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
<f:checkbox />
</f:entry>
<f:advanced>
<f:entry title="${%Shared secret}" field="secret">
<f:password />
</f:entry>
<f:entry title="${%Success message}" field="msgSuccess">
<f:textarea default="${descriptor.msgSuccess}"/>
</f:entry>
<f:entry title="${%Failure message}" field="msgFailure">
<f:textarea default="${descriptor.msgFailure}"/>
</f:entry>
<f:entry title="${%Trigger phrase}" field="triggerPhrase">
<f:textbox />
</f:entry>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// Namespaces
xml = namespace("http://www.w3.org/XML/1998/namespace")
j = namespace("jelly:core")
f = namespace("/lib/form")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public class GhprbPullRequestMergeTest {
private final String mergeComment = "merge";

private final Integer pullId = 1;

private Map<String, Object> triggerValues;

@Before
Expand Down