Skip to content
This repository has been archived by the owner on Jun 9, 2021. It is now read-only.

Commit

Permalink
Adding optional regular expression to injection feature #56
Browse files Browse the repository at this point in the history
 * To be able to extract crumb from Jenkins even if primitive XPath result sets forbidden
  • Loading branch information
tomasbjerre committed Sep 2, 2015
1 parent a602b53 commit bcd7841
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 15 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

Changelog of Pull Request Notifier for Stash.

## 1.27
* Adding an optional regular expression, that can be evaluated in the response from injection URL, to populate ${INJECTION_URL_VALUE}.

## 1.26
* Removing XPath alternative, introduced in version 1.22. It may not work in all installations. And is is not needed for Jenkins, which was the original use case.

Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ The Pull Request Notifier for Stash can:
* RESCOPED_FROM, when source branch change
* RESCOPED_TO, when target branch change
* BUTTON_TRIGGER, when trigger button in pull request view is pressed
* Can invoke CSRF protected systems, using the ${INJECTION_URL_VALUE} variable
* With Jenkins you set the Injection URL field to: `JENKINS_URL/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)`
* Can invoke CSRF protected systems, using the ${INJECTION_URL_VALUE} variable. How to to that with Jenkins is described below.
* Be configured to only trigger if the pull request mathches a filter. A filter text is constructed with any combination of the variables and then a regexp is constructed to match that text.
* Add buttons to pull request view in Stash. And map those buttons to URL invocations. This can be done by setting the filter string to ${BUTTON_TRIGGER_TITLE} and the filter regexp to title of button.
* Authenticate with HTTP basic authentication.
Expand Down Expand Up @@ -70,6 +69,16 @@ The ${PULL_REQUEST_USER...} contains information about the user who issued the e

You may want to use [Violation Comments to Stash plugin](https://wiki.jenkins-ci.org/display/JENKINS/Violation+Comments+to+Stash+Plugin) and/or [StashNotifier plugin](https://wiki.jenkins-ci.org/display/JENKINS/StashNotifier+Plugin) to report results back to Stash.

### Jenkins
Parameterized Jenkins jobs can be triggered remotely via:
```
http://server/job/theJob/buildWithParameters?token=TOKEN&PARAMETER=Value
```

If you are using a CSRF protection in Jenkins, you can use the **Injection URL** feature.
* Set **Injection URL** field to `http://JENKINS/crumbIssuer/api/xml?xpath=//crumb/text()`. You may get an error like *primitive XPath result sets forbidden; implement jenkins.security.SecureRequester*. If so, you can set Injection URL to `http://JENKINS/crumbIssuer/api/xml?xpath=//crumb` in combination with regular expression `<crumb>([^<]*)</crumb>`. Or a third option is to checkout [this](https://wiki.jenkins-ci.org/display/JENKINS/Secure+Requester+Whitelist+Plugin) Jenkins plugin.
* In the headers section, set header **.crumb** with value **${INJECTION_URL_VALUE}**.

## Developer instructions
Prerequisites:

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/se/bjurr/prnfs/admin/AdminFormValues.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public enum FIELDS {
button_visibility, //
admin_allowed, //
user_allowed, //
injection_url
injection_url, //
injection_url_regexp
}
}
17 changes: 15 additions & 2 deletions src/main/java/se/bjurr/prnfs/listener/PrnfsRenderer.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package se.bjurr.prnfs.listener;

import static java.util.logging.Logger.getLogger;
import static java.util.regex.Pattern.compile;
import static se.bjurr.prnfs.listener.PrnfsRenderer.REPO_PROTOCOL.http;
import static se.bjurr.prnfs.listener.PrnfsRenderer.REPO_PROTOCOL.ssh;
import static se.bjurr.prnfs.listener.UrlInvoker.urlInvoker;
Expand All @@ -9,6 +10,7 @@
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Matcher;

import se.bjurr.prnfs.settings.PrnfsNotification;

Expand Down Expand Up @@ -304,12 +306,23 @@ public String resolve(PullRequest pullRequest, PrnfsPullRequestAction pullReques
}
UrlInvoker urlInvoker = urlInvoker() //
.withUrlParam(prnfsNotification.getInjectionUrl().get()) //
.withMethod(GET).withProxyServer(prnfsNotification.getProxyServer()) //
.withMethod(GET)//
.withProxyServer(prnfsNotification.getProxyServer()) //
.withProxyPort(prnfsNotification.getProxyPort()) //
.withProxyUser(prnfsNotification.getProxyUser()) //
.withProxyPassword(prnfsNotification.getProxyPassword());
PrnfsRenderer.invoker.invoke(urlInvoker);
return urlInvoker.getResponseString().trim();
String rawResponse = urlInvoker.getResponseString().trim();
if (prnfsNotification.getInjectionUrlRegexp().isPresent()) {
Matcher m = compile(prnfsNotification.getInjectionUrlRegexp().get()).matcher(rawResponse);
if (!m.find()) {
logger.severe("Could not find \"" + prnfsNotification.getInjectionUrlRegexp().get() + "\" in:\n" + rawResponse);
return "";
}
return m.group(1);
} else {
return rawResponse;
}
}
});

Expand Down
27 changes: 18 additions & 9 deletions src/main/java/se/bjurr/prnfs/settings/PrnfsNotification.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import static java.util.regex.Pattern.compile;
import static se.bjurr.prnfs.admin.AdminFormValues.DEFAULT_NAME;
import static se.bjurr.prnfs.admin.AdminFormValues.VALUE;
import static se.bjurr.prnfs.admin.AdminFormValues.FIELDS.filter_regexp;
import static se.bjurr.prnfs.admin.AdminFormValues.FIELDS.filter_string;
import static se.bjurr.prnfs.admin.AdminFormValues.FORM_TYPE.TRIGGER_CONFIG_FORM;
import static se.bjurr.prnfs.listener.UrlInvoker.HTTP_METHOD.GET;
import static se.bjurr.prnfs.settings.PrnfsPredicates.predicate;

Expand All @@ -17,6 +20,7 @@
import java.util.Map;

import se.bjurr.prnfs.admin.AdminFormValues;
import se.bjurr.prnfs.admin.AdminFormValues.FIELDS;
import se.bjurr.prnfs.admin.AdminFormValues.FORM_TYPE;
import se.bjurr.prnfs.listener.PrnfsPullRequestAction;
import se.bjurr.prnfs.listener.UrlInvoker.HTTP_METHOD;
Expand All @@ -39,11 +43,12 @@ public class PrnfsNotification {
private final Integer proxyPort;
private final String name;
private final String injectionUrl;
private final String injectionUrlRegexp;

public PrnfsNotification(List<PrnfsPullRequestAction> triggers, String url, String user, String password,
String filterString, String filterRegexp, String method, String postContent, List<Header> headers, String proxyUser,
String proxyPassword, String proxyServer, String proxyPort, String name, String injectionUrl)
throws ValidationException {
String proxyPassword, String proxyServer, String proxyPort, String name, String injectionUrl,
String injectionUrlRegexp) throws ValidationException {
this.proxyUser = emptyToNull(nullToEmpty(proxyUser).trim());
this.proxyPassword = emptyToNull(nullToEmpty(proxyPassword).trim());
this.proxyServer = emptyToNull(nullToEmpty(proxyServer).trim());
Expand All @@ -52,23 +57,22 @@ public PrnfsNotification(List<PrnfsPullRequestAction> triggers, String url, Stri
this.postContent = emptyToNull(nullToEmpty(postContent).trim());
this.method = HTTP_METHOD.valueOf(firstNonNull(emptyToNull(nullToEmpty(method).trim()), GET.name()));
if (nullToEmpty(url).trim().isEmpty()) {
throw new ValidationException(AdminFormValues.FIELDS.url.name(), "URL not set!");
throw new ValidationException(FIELDS.url.name(), "URL not set!");
}
try {
new URL(url);
} catch (final Exception e) {
throw new ValidationException(AdminFormValues.FIELDS.url.name(), "URL not valid!");
throw new ValidationException(FIELDS.url.name(), "URL not valid!");
}
if (!nullToEmpty(filterRegexp).trim().isEmpty()) {
try {
compile(filterRegexp);
} catch (final Exception e) {
throw new ValidationException(AdminFormValues.FIELDS.filter_regexp.name(), "Filter regexp not valid! "
throw new ValidationException(filter_regexp.name(), "Filter regexp not valid! "
+ e.getMessage().replaceAll("\n", " "));
}
if (nullToEmpty(filterString).trim().isEmpty()) {
throw new ValidationException(AdminFormValues.FIELDS.filter_string.name(),
"Filter string not set, nothing to match regexp against!");
throw new ValidationException(filter_string.name(), "Filter string not set, nothing to match regexp against!");
}
}
this.url = url;
Expand All @@ -79,6 +83,7 @@ public PrnfsNotification(List<PrnfsPullRequestAction> triggers, String url, Stri
this.filterRegexp = filterRegexp;
this.name = firstNonNull(emptyToNull(nullToEmpty(name).trim()), DEFAULT_NAME);
this.injectionUrl = emptyToNull(nullToEmpty(injectionUrl).trim());
this.injectionUrlRegexp = emptyToNull(nullToEmpty(injectionUrlRegexp).trim());
}

public Optional<String> getFilterRegexp() {
Expand Down Expand Up @@ -139,11 +144,15 @@ public List<Header> getHeaders() {

public static boolean isOfType(AdminFormValues config, FORM_TYPE formType) {
Optional<Map<String, String>> formTypeOpt = tryFind(config, predicate(AdminFormValues.FIELDS.FORM_TYPE.name()));
return !formTypeOpt.isPresent() && formType.name().equals(FORM_TYPE.TRIGGER_CONFIG_FORM.name())
|| formTypeOpt.get().get(VALUE).equals(AdminFormValues.FORM_TYPE.TRIGGER_CONFIG_FORM.name());
return !formTypeOpt.isPresent() && formType.name().equals(TRIGGER_CONFIG_FORM.name())
|| formTypeOpt.get().get(VALUE).equals(TRIGGER_CONFIG_FORM.name());
}

public Optional<String> getInjectionUrl() {
return fromNullable(injectionUrl);
}

public Optional<String> getInjectionUrlRegexp() {
return fromNullable(injectionUrlRegexp);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,26 @@ public static PrnfsNotificationBuilder prnfsNotificationBuilder() {
private String proxyPort;
private String name;
private String injectionUrl;
private String injectionUrlRegexp;

private PrnfsNotificationBuilder() {
}

public PrnfsNotification build() throws ValidationException {
return new PrnfsNotification(triggers, url, user, password, filterString, filterRegexp, method, postContent, headers,
proxyUser, proxyPassword, proxyServer, proxyPort, name, injectionUrl);
proxyUser, proxyPassword, proxyServer, proxyPort, name, injectionUrl, injectionUrlRegexp);
}

public PrnfsNotificationBuilder withInjectionUrl(String injectionUrl) {
this.injectionUrl = checkNotNull(injectionUrl);
return this;
}

public PrnfsNotificationBuilder withInjectionUrlRegexp(String injectionUrlRegexp) {
this.injectionUrlRegexp = checkNotNull(injectionUrlRegexp);
return this;
}

public PrnfsNotificationBuilder withPassword(String password) {
this.password = checkNotNull(password);
return this;
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/se/bjurr/prnfs/settings/SettingsStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ public static PrnfsNotification getPrnfsNotification(AdminFormValues adminFormVa
prnfsNotificationBuilder.withInjectionUrl(find(adminFormValues,
predicate(AdminFormValues.FIELDS.injection_url.name())).get(VALUE));
}
if (tryFind(adminFormValues, predicate(AdminFormValues.FIELDS.injection_url_regexp.name())).isPresent()) {
prnfsNotificationBuilder.withInjectionUrlRegexp(find(adminFormValues,
predicate(AdminFormValues.FIELDS.injection_url_regexp.name())).get(VALUE));
}
return prnfsNotificationBuilder.build();
}

Expand Down
1 change: 1 addition & 0 deletions src/main/resources/admin.vm
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@
<fieldset>
<legend>Injection URL (Optional)</legend>
<label>URL<input type="text" name="injection_url"></label>
<label>Optional regular expression <input type="text" name="injection_url_regexp"></label>
<p>
The (GET) response from this URL will be available in the ${INJECTION_URL_VALUE} variable.
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static se.bjurr.prnfs.admin.AdminFormValues.FIELDS.header_name;
import static se.bjurr.prnfs.admin.AdminFormValues.FIELDS.header_value;
import static se.bjurr.prnfs.admin.AdminFormValues.FIELDS.injection_url;
import static se.bjurr.prnfs.admin.AdminFormValues.FIELDS.injection_url_regexp;
import static se.bjurr.prnfs.admin.AdminFormValues.FIELDS.method;
import static se.bjurr.prnfs.admin.AdminFormValues.FIELDS.password;
import static se.bjurr.prnfs.admin.AdminFormValues.FIELDS.post_content;
Expand Down Expand Up @@ -1611,6 +1612,31 @@ public void testThatValueFromUrlCanBeUsedInInvocation() throws Exception {
.invokedOnlyUrl("http://bjurr.se/?some%20content");
}

@Test
public void testThatValueFromUrlAndRegexpCanBeUsedInInvocation() throws Exception {
prnfsTestBuilder() //
.isLoggedInAsAdmin() //
.withNotification( //
notificationBuilder() //
.withFieldValue(url, "http://bjurr.se/?${" + INJECTION_URL_VALUE + "}") //
.withFieldValue(events, OPENED.name()) //
.withFieldValue(FORM_TYPE, TRIGGER_CONFIG_FORM.name()) //
.withFieldValue(injection_url, "http://bjurr.se/get") //
.withFieldValue(injection_url_regexp, "<crumb>([^<]*)</crumb>") //
.build() //
) //
.store() //
.withResponse(
"http://bjurr.se/get",
" \n <defaultCrumbIssuer><crumb>986a2d65b7987258434da5583c9f5337</crumb><crumbRequestField>.crumb</crumbRequestField></defaultCrumbIssuer> \n ") //
.trigger( //
pullRequestEventBuilder() //
.withPullRequestAction(OPENED) //
.build() //
) //
.invokedOnlyUrl("http://bjurr.se/?986a2d65b7987258434da5583c9f5337");
}

@Test
public void testThatProxyMayNotBeUsedWhenInvokingUrl() throws Exception {
prnfsTestBuilder()//
Expand Down

0 comments on commit bcd7841

Please sign in to comment.