Skip to content

Commit

Permalink
Authorization Bearer #43
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasbjerre committed Feb 24, 2018
1 parent 5799d52 commit c35998f
Show file tree
Hide file tree
Showing 12 changed files with 336 additions and 172 deletions.
24 changes: 14 additions & 10 deletions README.md
Expand Up @@ -56,6 +56,20 @@ When using the plugin in several jobs, you will have the same URL trigger all jo

Available in Jenkins [here](https://wiki.jenkins-ci.org/display/JENKINS/Generic+Webhook+Trigger+Plugin).

## Authentication

There is a special `token` parameter. When supplied, it is used to authenticate with [BuildAuthorizationToken](http://javadoc.jenkins-ci.org/hudson/model/BuildAuthorizationToken.html) to authenticate.

It might be a good idea to have a different token for each job. Then only that job will be visible for that request. This will increase performance and reduce responses of each invocation.

![Parameter](https://github.com/jenkinsci/generic-webhook-trigger-plugin/blob/master/sandbox/configure-token.png)

The token can be supplied

* As a request parameter: `curl -vs http://localhost:8080/jenkins/generic-webhook-trigger/invoke?token=abc123 2>&1`
* As a token header: `curl -vs -H "token: abc123" http://localhost:8080/jenkins/generic-webhook-trigger/invoke 2>&1`
* As *Authorization* header of type *Bearer* : `curl -vs -H "Authorization : Bearer abc123" http://localhost:8080/jenkins/generic-webhook-trigger/invoke 2>&1`

## Troubleshooting

It's probably easiest to do with curl. Given that you have configured a Jenkins job to trigger on Generic Webhook, here are some examples of how to start the jobs.
Expand All @@ -76,16 +90,6 @@ And to authenticate in the request you may try this.
curl -vs http://username:password@localhost:8080/generic-webhook-trigger/invoke 2>&1
```

There is also a special request parameter named `token`. When supplied, it is used to authenticate with [BuildAuthorizationToken](http://javadoc.jenkins-ci.org/hudson/model/BuildAuthorizationToken.html).

![Parameter](https://github.com/jenkinsci/generic-webhook-trigger-plugin/blob/master/sandbox/configure-token.png)

The job can then be triggered with that token like this.

```
curl -vs http://localhost:8080/jenkins/generic-webhook-trigger/invoke?token=abc123 2>&1
```

If you want to trigger with some post content, curl can dot that like this.
```
curl -v -H "Content-Type: application/json" -X POST -d '{ "app":{ "name":"GitHub API", "url":"http://developer.github.com/v3/oauth/" }}' http://localhost:8080/jenkins/generic-webhook-trigger/invoke?token=sometoken
Expand Down
57 changes: 28 additions & 29 deletions src/main/java/org/jenkinsci/plugins/gwt/GenericTrigger.java
Expand Up @@ -4,34 +4,33 @@
import static com.google.common.collect.Lists.newArrayList;
import static java.util.logging.Level.INFO;
import static java.util.regex.Pattern.compile;
import hudson.Extension;
import hudson.model.Item;
import hudson.model.ParameterValue;
import hudson.model.CauseAction;
import hudson.model.Job;
import hudson.model.ParametersAction;
import hudson.model.StringParameterValue;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;

import jenkins.model.ParameterizedJobMixIn;

import org.jenkinsci.plugins.gwt.resolvers.VariablesResolver;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

import com.google.common.annotations.VisibleForTesting;

import hudson.Extension;
import hudson.model.CauseAction;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.ParameterValue;
import hudson.model.ParametersAction;
import hudson.model.StringParameterValue;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
import jenkins.model.ParameterizedJobMixIn;

public class GenericTrigger extends Trigger<Job<?, ?>> {

private static final Logger LOGGER = Logger.getLogger(GenericTrigger.class.getName());
Expand All @@ -46,7 +45,7 @@ public class GenericTrigger extends Trigger<Job<?, ?>> {
public static class GenericDescriptor extends TriggerDescriptor {

@Override
public boolean isApplicable(Item item) {
public boolean isApplicable(final Item item) {
return Job.class.isAssignableFrom(item.getClass());
}

Expand All @@ -58,11 +57,11 @@ public String getDisplayName() {

@DataBoundConstructor
public GenericTrigger(
List<GenericVariable> genericVariables,
String regexpFilterText,
String regexpFilterExpression,
List<GenericRequestVariable> genericRequestVariables,
List<GenericHeaderVariable> genericHeaderVariables) {
final List<GenericVariable> genericVariables,
final String regexpFilterText,
final String regexpFilterExpression,
final List<GenericRequestVariable> genericRequestVariables,
final List<GenericHeaderVariable> genericHeaderVariables) {
this.genericVariables = genericVariables;
this.regexpFilterExpression = regexpFilterExpression;
this.regexpFilterText = regexpFilterText;
Expand All @@ -71,12 +70,12 @@ public GenericTrigger(
}

@DataBoundSetter
public void setPrintContributedVariables(boolean printContributedVariables) {
public void setPrintContributedVariables(final boolean printContributedVariables) {
this.printContributedVariables = printContributedVariables;
}

@DataBoundSetter
public void setPrintPostContent(boolean printPostContent) {
public void setPrintPostContent(final boolean printPostContent) {
this.printPostContent = printPostContent;
}

Expand All @@ -92,9 +91,9 @@ public boolean isPrintPostContent() {

@SuppressWarnings("static-access")
public GenericTriggerResults trigger(
Map<String, Enumeration<String>> headers,
Map<String, String[]> parameterMap,
String postContent) {
final Map<String, List<String>> headers,
final Map<String, String[]> parameterMap,
final String postContent) {
final Map<String, String> resolvedVariables =
new VariablesResolver(
headers,
Expand Down Expand Up @@ -133,7 +132,7 @@ public GenericTriggerResults trigger(
};
}

private ParametersAction createParameters(Map<String, String> resolvedVariables) {
private ParametersAction createParameters(final Map<String, String> resolvedVariables) {
final List<ParameterValue> parameterList = newArrayList();
for (final Entry<String, String> entry : resolvedVariables.entrySet()) {
final ParameterValue parameter = new StringParameterValue(entry.getKey(), entry.getValue());
Expand All @@ -143,7 +142,7 @@ private ParametersAction createParameters(Map<String, String> resolvedVariables)
}

@VisibleForTesting
boolean isMatching(String renderedRegexpFilterText, String regexpFilterExpression) {
boolean isMatching(final String renderedRegexpFilterText, final String regexpFilterExpression) {
final boolean noFilterConfigured =
isNullOrEmpty(renderedRegexpFilterText) || isNullOrEmpty(regexpFilterExpression);
if (noFilterConfigured) {
Expand All @@ -166,7 +165,7 @@ boolean isMatching(String renderedRegexpFilterText, String regexpFilterExpressio
}

@VisibleForTesting
String renderText(String regexpFilterText, Map<String, String> resolvedVariables) {
String renderText(String regexpFilterText, final Map<String, String> resolvedVariables) {
if (isNullOrEmpty(regexpFilterText)) {
return "";
}
Expand All @@ -186,13 +185,13 @@ String renderText(String regexpFilterText, Map<String, String> resolvedVariables
}

@VisibleForTesting
List<String> getVariablesInResolveOrder(Set<String> unsorted) {
final List<String> variables = new ArrayList<String>(unsorted);
List<String> getVariablesInResolveOrder(final Set<String> unsorted) {
final List<String> variables = new ArrayList<>(unsorted);
Collections.sort(
variables,
new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
public int compare(final String o1, final String o2) {
if (o1.length() == o2.length()) {
return o1.compareTo(o2);
} else if (o1.length() > o2.length()) {
Expand Down
Expand Up @@ -5,11 +5,9 @@
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.SEVERE;
import hudson.Extension;
import hudson.model.UnprotectedRootAction;
import hudson.security.csrf.CrumbExclusion;

import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
Expand All @@ -27,6 +25,10 @@

import com.google.common.annotations.VisibleForTesting;

import hudson.Extension;
import hudson.model.UnprotectedRootAction;
import hudson.security.csrf.CrumbExclusion;

@Extension
public class GenericWebHookRequestReceiver extends CrumbExclusion implements UnprotectedRootAction {

Expand All @@ -39,10 +41,10 @@ public class GenericWebHookRequestReceiver extends CrumbExclusion implements Unp
private static final Logger LOGGER =
Logger.getLogger(GenericWebHookRequestReceiver.class.getName());

public HttpResponse doInvoke(StaplerRequest request) {
public HttpResponse doInvoke(final StaplerRequest request) {
String postContent = null;
Map<String, String[]> parameterMap = null;
Map<String, Enumeration<String>> headers = null;
Map<String, List<String>> headers = null;
try {
headers = getHeaders(request);
parameterMap = request.getParameterMap();
Expand All @@ -51,29 +53,48 @@ public HttpResponse doInvoke(StaplerRequest request) {
LOGGER.log(SEVERE, "", e);
}

final String token =
request.getParameter("token") != null ? request.getParameter("token").trim() : null;
return doInvoke(headers, parameterMap, postContent, token);
final String givenToken = getGivenToken(headers, parameterMap);
return doInvoke(headers, parameterMap, postContent, givenToken);
}

@VisibleForTesting
String getGivenToken(
final Map<String, List<String>> headers, final Map<String, String[]> parameterMap) {
if (parameterMap.containsKey("token")) {
return parameterMap.get("token")[0];
}
if (headers.containsKey("token")) {
return headers.get("token").get(0);
}
if (headers.containsKey("Authorization")) {
for (final String candidateValue : headers.get("Authorization")) {
if (candidateValue.startsWith("Bearer ")) {
return candidateValue.substring(7);
}
}
}
return null;
}

private Map<String, Enumeration<String>> getHeaders(StaplerRequest request) {
final Map<String, Enumeration<String>> headers = new HashMap<>();
@VisibleForTesting
Map<String, List<String>> getHeaders(final StaplerRequest request) {
final Map<String, List<String>> headers = new HashMap<>();
final Enumeration<String> headersEnumeration = request.getHeaderNames();
while (headersEnumeration.hasMoreElements()) {
final String headerName = headersEnumeration.nextElement();
headers.put(headerName, request.getHeaders(headerName));
headers.put(headerName, Collections.list(request.getHeaders(headerName)));
}
return headers;
}

@VisibleForTesting
HttpResponse doInvoke(
Map<String, Enumeration<String>> headers,
Map<String, String[]> parameterMap,
String postContent,
String token) {
final Map<String, List<String>> headers,
final Map<String, String[]> parameterMap,
final String postContent,
final String givenToken) {

final List<FoundJob> foundJobs = JobFinder.findAllJobsWithTrigger(token);
final List<FoundJob> foundJobs = JobFinder.findAllJobsWithTrigger(givenToken);
final Map<String, Object> triggerResultsMap = new HashMap<>();
if (foundJobs.isEmpty()) {
LOGGER.log(INFO, NO_JOBS_MSG);
Expand Down Expand Up @@ -118,7 +139,7 @@ public String getUrlName() {

@Override
public boolean process(
HttpServletRequest request, HttpServletResponse response, FilterChain chain)
final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain)
throws IOException, ServletException {
final String pathInfo = request.getPathInfo();
if (pathInfo != null && pathInfo.startsWith("/" + URL_NAME + "/")) {
Expand Down
27 changes: 14 additions & 13 deletions src/main/java/org/jenkinsci/plugins/gwt/JobFinder.java
@@ -1,25 +1,25 @@
package org.jenkinsci.plugins.gwt;

import static com.google.common.base.Strings.isNullOrEmpty;
import hudson.security.ACL;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import jenkins.model.Jenkins;
import jenkins.model.ParameterizedJobMixIn.ParameterizedJob;

import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;

import hudson.security.ACL;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
import jenkins.model.Jenkins;
import jenkins.model.ParameterizedJobMixIn.ParameterizedJob;

public final class JobFinder {

private JobFinder() {}

public static List<FoundJob> findAllJobsWithTrigger(String queryStringToken) {
public static List<FoundJob> findAllJobsWithTrigger(final String givenToken) {

final List<FoundJob> found = new ArrayList<>();

Expand All @@ -34,7 +34,7 @@ public static List<FoundJob> findAllJobsWithTrigger(String queryStringToken) {
candidateProjects = getAllParameterizedJobsByImpersonation();
for (final ParameterizedJob candidateJob : candidateProjects) {
if (!isIncluded(candidateJob.getFullName(), found)
&& authenticationTokenMatches(candidateJob, queryStringToken)) {
&& authenticationTokenMatches(candidateJob, givenToken)) {
final GenericTrigger genericTriggerOpt = findGenericTrigger(candidateJob.getTriggers());
if (genericTriggerOpt != null) {
found.add(new FoundJob(candidateJob.getFullName(), genericTriggerOpt));
Expand All @@ -45,7 +45,7 @@ && authenticationTokenMatches(candidateJob, queryStringToken)) {
return found;
}

private static boolean isIncluded(String searchFor, List<FoundJob> includedJobs) {
private static boolean isIncluded(final String searchFor, final List<FoundJob> includedJobs) {
for (final FoundJob includedJob : includedJobs) {
if (includedJob.getFullName().equals(searchFor)) {
return true;
Expand All @@ -62,12 +62,12 @@ private static List<ParameterizedJob> getAllParameterizedJobs() {

@SuppressWarnings("deprecation")
private static boolean authenticationTokenMatches(
ParameterizedJob candidateJob, String queryStringToken) {
final ParameterizedJob candidateJob, final String givenToken) {
final hudson.model.BuildAuthorizationToken authToken = candidateJob.getAuthToken();

final boolean jobHasAuthToken = authToken != null && !isNullOrEmpty(authToken.getToken());
if (jobHasAuthToken) {
final boolean authTokenMatchesQueryToken = authToken.getToken().equals(queryStringToken);
if (jobHasAuthToken && givenToken != null) {
final boolean authTokenMatchesQueryToken = authToken.getToken().equals(givenToken);
if (authTokenMatchesQueryToken) {
return true;
} else {
Expand All @@ -86,7 +86,8 @@ private static List<ParameterizedJob> getAllParameterizedJobsByImpersonation() {
return jobs;
}

private static GenericTrigger findGenericTrigger(Map<TriggerDescriptor, Trigger<?>> triggers) {
private static GenericTrigger findGenericTrigger(
final Map<TriggerDescriptor, Trigger<?>> triggers) {
if (triggers == null) {
return null;
}
Expand Down

0 comments on commit c35998f

Please sign in to comment.