Compare commits
29 Commits
feature/co
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
0c0bf0b244 | ||
|
c7f7163aaa | ||
|
36e77b1c7a | ||
|
5189c62651 | ||
|
75f406845f | ||
|
105b019e2b | ||
|
4f00a26293 | ||
|
2a36f24269 | ||
|
1ec494fb0d | ||
|
e329b6d9ff | ||
|
181672afcc | ||
|
d523af1b6c | ||
|
390e3bd772 | ||
|
21549749c0 | ||
|
6184509494 | ||
|
b1f69564a3 | ||
|
159cb4a911 | ||
|
b5af9a3968 | ||
|
2e4aea4cbc | ||
|
b6e5b11174 | ||
|
9dec52c727 | ||
|
f2b536dbad | ||
|
62f9c01cab | ||
|
6d2f26d952 | ||
|
c2ffd465c4 | ||
|
3e18a060ae | ||
|
6f5962f8f2 | ||
|
346c761ef6 | ||
|
9c7f8c1ac4 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,4 +3,4 @@
|
|||||||
coverage
|
coverage
|
||||||
webhook
|
webhook
|
||||||
/test/hookecho
|
/test/hookecho
|
||||||
go_build_webhook*
|
build
|
||||||
|
13
README.md
13
README.md
@ -22,6 +22,9 @@ Everything else is the responsibility of the command's author.
|
|||||||
|
|
||||||
If you don't have time to waste configuring, hosting, debugging and maintaining your webhook instance, we offer a __SaaS__ solution that has all of the capabilities webhook provides, plus a lot more, and all that packaged in a nice friendly web interface. If you are interested, find out more at [hookdoo website](https://www.hookdoo.com/?ref=github-webhook-readme). If you have any questions, you can contact us at info@hookdoo.com
|
If you don't have time to waste configuring, hosting, debugging and maintaining your webhook instance, we offer a __SaaS__ solution that has all of the capabilities webhook provides, plus a lot more, and all that packaged in a nice friendly web interface. If you are interested, find out more at [hookdoo website](https://www.hookdoo.com/?ref=github-webhook-readme). If you have any questions, you can contact us at info@hookdoo.com
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
|
<a href="https://www.hookdeck.com/?ref=adnanh-webhook"><img src="http://hajdarevic.net/hookdeck-logo.svg" height="17" alt="hookdeck" align="left" /></a> If you need a way of inspecting, monitoring and replaying webhooks without the back and forth troubleshooting, [give Hookdeck a try!](https://www.hookdeck.com/?ref=adnanh-webhook)
|
||||||
|
|
||||||
# Getting started
|
# Getting started
|
||||||
## Installation
|
## Installation
|
||||||
@ -110,13 +113,17 @@ TLS version and cipher suite selection flags are available from the command line
|
|||||||
If you want to set CORS headers, you can use the `-header name=value` flag while starting [webhook][w] to set the appropriate CORS headers that will be returned with each response.
|
If you want to set CORS headers, you can use the `-header name=value` flag while starting [webhook][w] to set the appropriate CORS headers that will be returned with each response.
|
||||||
|
|
||||||
## Interested in running webhook inside of a Docker container?
|
## Interested in running webhook inside of a Docker container?
|
||||||
You can use [almir/webhook](https://hub.docker.com/r/almir/webhook/) docker image, or create your own (please read [this discussion](https://github.com/adnanh/webhook/issues/63)).
|
You can use one of the following Docker images, or create your own (please read [this discussion](https://github.com/adnanh/webhook/issues/63)):
|
||||||
|
- [almir/webhook](https://github.com/almir/docker-webhook)
|
||||||
|
- [roxedus/webhook](https://github.com/Roxedus/docker-webhook)
|
||||||
|
- [thecatlady/webhook](https://github.com/thecatlady/docker-webhook)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
Check out [Hook examples page](docs/Hook-Examples.md) for more complex examples of hooks.
|
Check out [Hook examples page](docs/Hook-Examples.md) for more complex examples of hooks.
|
||||||
|
|
||||||
### Guides featuring webhook
|
### Guides featuring webhook
|
||||||
- [Webhook & JIRA](https://sites.google.com/site/mrxpalmeiras/notes/jira-webhooks) by [@perfecto25](https://github.com/perfecto25)
|
- [Plex 2 Telegram](https://gitlab.com/-/snippets/1972594) by [@psyhomb](https://github.com/psyhomb)
|
||||||
|
- [Webhook & JIRA](https://sites.google.com/site/mrxpalmeiras/more/jira-webhooks) by [@perfecto25](https://github.com/perfecto25)
|
||||||
- [Trigger Ansible AWX job runs on SCM (e.g. git) commit](http://jpmens.net/2017/10/23/trigger-awx-job-runs-on-scm-commit/) by [@jpmens](http://mens.de/)
|
- [Trigger Ansible AWX job runs on SCM (e.g. git) commit](http://jpmens.net/2017/10/23/trigger-awx-job-runs-on-scm-commit/) by [@jpmens](http://mens.de/)
|
||||||
- [Deploy using GitHub webhooks](https://davidauthier.wearemd.com/blog/deploy-using-github-webhooks.html) by [@awea](https://davidauthier.wearemd.com)
|
- [Deploy using GitHub webhooks](https://davidauthier.wearemd.com/blog/deploy-using-github-webhooks.html) by [@awea](https://davidauthier.wearemd.com)
|
||||||
- [Setting up Automatic Deployment and Builds Using Webhooks](https://willbrowning.me/setting-up-automatic-deployment-and-builds-using-webhooks/) by [Will Browning](https://willbrowning.me/about/)
|
- [Setting up Automatic Deployment and Builds Using Webhooks](https://willbrowning.me/setting-up-automatic-deployment-and-builds-using-webhooks/) by [Will Browning](https://willbrowning.me/about/)
|
||||||
@ -131,6 +138,8 @@ Check out [Hook examples page](docs/Hook-Examples.md) for more complex examples
|
|||||||
- [XiaoMi Vacuum + Amazon Button = Dash Cleaning](https://www.instructables.com/id/XiaoMi-Vacuum-Amazon-Button-Dash-Cleaning/) by [c0mmensal](https://www.instructables.com/member/c0mmensal/)
|
- [XiaoMi Vacuum + Amazon Button = Dash Cleaning](https://www.instructables.com/id/XiaoMi-Vacuum-Amazon-Button-Dash-Cleaning/) by [c0mmensal](https://www.instructables.com/member/c0mmensal/)
|
||||||
- [Set up Automated Deployments From Github With Webhook](https://maximorlov.com/automated-deployments-from-github-with-webhook/) by [Maxim Orlov](https://twitter.com/_maximization)
|
- [Set up Automated Deployments From Github With Webhook](https://maximorlov.com/automated-deployments-from-github-with-webhook/) by [Maxim Orlov](https://twitter.com/_maximization)
|
||||||
- VIDEO: [Gitlab CI/CD configuration using Docker and adnanh/webhook to deploy on VPS - Tutorial #1](https://www.youtube.com/watch?v=Qhn-lXjyrZA&feature=youtu.be) by [Yes! Let's Learn Software Engineering](https://www.youtube.com/channel/UCH4XJf2BZ_52fbf8fOBMF3w)
|
- VIDEO: [Gitlab CI/CD configuration using Docker and adnanh/webhook to deploy on VPS - Tutorial #1](https://www.youtube.com/watch?v=Qhn-lXjyrZA&feature=youtu.be) by [Yes! Let's Learn Software Engineering](https://www.youtube.com/channel/UCH4XJf2BZ_52fbf8fOBMF3w)
|
||||||
|
- [Integrate automatic deployment in 20 minutes using webhooks + Nginx setup](https://anksus.me/blog/integrate-automatic-deployment-in-20-minutes-using-webhooks) by [Anksus](https://github.com/Anksus)
|
||||||
|
- [Automatically redeploy your static blog with Gitea, Uberspace & Webhook](https://by.arran.nz/posts/code/webhook-deploy/) by [Arran](https://arran.nz)
|
||||||
- ...
|
- ...
|
||||||
- Want to add your own? Open an Issue or create a PR :-)
|
- Want to add your own? Open an Issue or create a PR :-)
|
||||||
|
|
||||||
|
@ -22,18 +22,7 @@ Hooks are defined as objects in the JSON or YAML hooks configuration file. Pleas
|
|||||||
* `pass-file-to-command` - specifies a list of entries that will be serialized as a file. Incoming [data](Referencing-Request-Values.md) will be serialized in a request-temporary-file (otherwise parallel calls of the hook would lead to concurrent overwritings of the file). The filename to be addressed within the subsequent script is provided via an environment variable. Use `envname` to specify the name of the environment variable. If `envname` is not provided `HOOK_` and the name used to reference the request value are used. Defining `command-working-directory` will store the file relative to this location, if not provided, the systems temporary file directory will be used. If `base64decode` is true, the incoming binary data will be base 64 decoded prior to storing it into the file. By default the corresponding file will be removed after the webhook exited.
|
* `pass-file-to-command` - specifies a list of entries that will be serialized as a file. Incoming [data](Referencing-Request-Values.md) will be serialized in a request-temporary-file (otherwise parallel calls of the hook would lead to concurrent overwritings of the file). The filename to be addressed within the subsequent script is provided via an environment variable. Use `envname` to specify the name of the environment variable. If `envname` is not provided `HOOK_` and the name used to reference the request value are used. Defining `command-working-directory` will store the file relative to this location, if not provided, the systems temporary file directory will be used. If `base64decode` is true, the incoming binary data will be base 64 decoded prior to storing it into the file. By default the corresponding file will be removed after the webhook exited.
|
||||||
* `trigger-rule` - specifies the rule that will be evaluated in order to determine should the hook be triggered. Check [Hook rules page](Hook-Rules.md) to see the list of valid rules and their usage
|
* `trigger-rule` - specifies the rule that will be evaluated in order to determine should the hook be triggered. Check [Hook rules page](Hook-Rules.md) to see the list of valid rules and their usage
|
||||||
* `trigger-rule-mismatch-http-response-code` - specifies the HTTP status code to be returned when the trigger rule is not satisfied
|
* `trigger-rule-mismatch-http-response-code` - specifies the HTTP status code to be returned when the trigger rule is not satisfied
|
||||||
* `pre-hook-command` - specifies the command that will be run before the hook gets invoked. Check [Pre-hook command page](PreHook-Command.md) for more details and examples.
|
* `trigger-signature-soft-failures` - allow signature validation failures within Or rules; by default, signature failures are treated as errors.
|
||||||
* to the STDIN of this command, webhook will pass a JSON string representation of an object with the following properties:
|
|
||||||
* `hookID` - ID of the hook that got matched
|
|
||||||
* `method` - HTTP(s) method used by the client (i.e. GET, POST, etc...)
|
|
||||||
* `URI` - URI which client requested
|
|
||||||
* `host` - value of the `Host` header sent by the client
|
|
||||||
* `remoteAddr` - client's IP address and port in the `IP:PORT` format
|
|
||||||
* `query` - object with query parameters and their respective values
|
|
||||||
* `headers` - object with headers and their respective values
|
|
||||||
* `base64EncodedBody` - base64 encoded request body
|
|
||||||
* Output of this command __MUST__ be valid JSON string which will be parsed by the webhook and accessible using the `pre-hook` as source when referencing values.
|
|
||||||
* __Important! Any errors encountered while trying to execute the pre-hook command will prevent the hook from triggering!__
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
Check out [Hook examples page](Hook-Examples.md) for more complex examples of hooks.
|
Check out [Hook examples page](Hook-Examples.md) for more complex examples of hooks.
|
||||||
|
@ -20,8 +20,12 @@ although the examples on this page all use the JSON format.
|
|||||||
* [Travis CI webhook](#travis-ci-webhook)
|
* [Travis CI webhook](#travis-ci-webhook)
|
||||||
* [XML Payload](#xml-payload)
|
* [XML Payload](#xml-payload)
|
||||||
* [Multipart Form Data](#multipart-form-data)
|
* [Multipart Form Data](#multipart-form-data)
|
||||||
|
* [Pass string arguments to command](#pass-string-arguments-to-command)
|
||||||
|
|
||||||
## Incoming Github webhook
|
## Incoming Github webhook
|
||||||
|
|
||||||
|
This example works on 2.8+ versions of Webhook - if you are on a previous series, change `payload-hmac-sha1` to `payload-hash-sha1`.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -79,7 +83,7 @@ although the examples on this page all use the JSON format.
|
|||||||
|
|
||||||
## Incoming Bitbucket webhook
|
## Incoming Bitbucket webhook
|
||||||
|
|
||||||
Bitbucket does not pass any secrets back to the webhook. [Per their documentation](https://confluence.atlassian.com/bitbucket/manage-webhooks-735643732.html#Managewebhooks-trigger_webhookTriggeringwebhooks), in order to verify that the webhook came from Bitbucket you must whitelist the IP range `104.192.143.0/24`:
|
Bitbucket does not pass any secrets back to the webhook. [Per their documentation](https://support.atlassian.com/organization-administration/docs/ip-addresses-and-domains-for-atlassian-cloud-products/#Outgoing-Connections), in order to verify that the webhook came from Bitbucket you must whitelist a set of IP ranges:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
@ -96,11 +100,23 @@ Bitbucket does not pass any secrets back to the webhook. [Per their documentati
|
|||||||
],
|
],
|
||||||
"trigger-rule":
|
"trigger-rule":
|
||||||
{
|
{
|
||||||
"match":
|
"or":
|
||||||
{
|
[
|
||||||
"type": "ip-whitelist",
|
{ "match": { "type": "ip-whitelist", "ip-range": "13.52.5.96/28" } },
|
||||||
"ip-range": "104.192.143.0/24"
|
{ "match": { "type": "ip-whitelist", "ip-range": "13.236.8.224/28" } },
|
||||||
}
|
{ "match": { "type": "ip-whitelist", "ip-range": "18.136.214.96/28" } },
|
||||||
|
{ "match": { "type": "ip-whitelist", "ip-range": "18.184.99.224/28" } },
|
||||||
|
{ "match": { "type": "ip-whitelist", "ip-range": "18.234.32.224/28" } },
|
||||||
|
{ "match": { "type": "ip-whitelist", "ip-range": "18.246.31.224/28" } },
|
||||||
|
{ "match": { "type": "ip-whitelist", "ip-range": "52.215.192.224/28" } },
|
||||||
|
{ "match": { "type": "ip-whitelist", "ip-range": "104.192.137.240/28" } },
|
||||||
|
{ "match": { "type": "ip-whitelist", "ip-range": "104.192.138.240/28" } },
|
||||||
|
{ "match": { "type": "ip-whitelist", "ip-range": "104.192.140.240/28" } },
|
||||||
|
{ "match": { "type": "ip-whitelist", "ip-range": "104.192.142.240/28" } },
|
||||||
|
{ "match": { "type": "ip-whitelist", "ip-range": "104.192.143.240/28" } },
|
||||||
|
{ "match": { "type": "ip-whitelist", "ip-range": "185.166.143.240/28" } },
|
||||||
|
{ "match": { "type": "ip-whitelist", "ip-range": "185.166.142.240/28" } }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -308,7 +324,7 @@ __Not recommended in production due to low security__
|
|||||||
```
|
```
|
||||||
|
|
||||||
## JIRA Webhooks
|
## JIRA Webhooks
|
||||||
[Guide by @perfecto25](https://sites.google.com/site/mrxpalmeiras/notes/jira-webhooks)
|
[Guide by @perfecto25](https://sites.google.com/site/mrxpalmeiras/more/jira-webhooks)
|
||||||
|
|
||||||
## Pass File-to-command sample
|
## Pass File-to-command sample
|
||||||
|
|
||||||
@ -589,3 +605,34 @@ Content-Disposition: form-data; name="thumb"; filename="thumb.jpg"
|
|||||||
```
|
```
|
||||||
|
|
||||||
We key off of the `name` attribute in the `Content-Disposition` value.
|
We key off of the `name` attribute in the `Content-Disposition` value.
|
||||||
|
|
||||||
|
## Pass string arguments to command
|
||||||
|
|
||||||
|
To pass simple string arguments to a command, use the `string` parameter source.
|
||||||
|
The following example will pass two static string parameters ("-e 123123") to the
|
||||||
|
`execute-command` before appending the `pusher.email` value from the payload:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "webhook",
|
||||||
|
"execute-command": "/home/adnan/redeploy-go-webhook.sh",
|
||||||
|
"command-working-directory": "/home/adnan/go",
|
||||||
|
"pass-arguments-to-command":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"source": "string",
|
||||||
|
"name": "-e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "string",
|
||||||
|
"name": "123123"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "pusher.email"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
# Pre-hook command
|
|
||||||
To the STDIN of the pre-hook command, webhook will pass a JSON string representation of an object with the following properties:
|
|
||||||
* `hookID` - ID of the hook that got matched
|
|
||||||
* `method` - HTTP(s) method used by the client (i.e. GET, POST, etc...)
|
|
||||||
* `URI` - URI which client requested
|
|
||||||
* `host` - value of the `Host` header sent by the client
|
|
||||||
* `remoteAddr` - client's IP address and port in the `IP:PORT` format
|
|
||||||
* `query` - object with query parameters and their respective values
|
|
||||||
* `headers` - object with headers and their respective values
|
|
||||||
* `base64EncodedBody` - base64 encoded request body
|
|
||||||
|
|
||||||
_Please note!_ Output of this command __MUST__ be valid JSON string which will be parsed by the webhook and accessible using the `pre-hook` as source when referencing values.
|
|
||||||
|
|
||||||
__Important! Any errors encountered while trying to execute the pre-hook command will prevent the hook from triggering!__
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
|
|
||||||
_Please note:_ Following examples use shell scripts as pre-hook commands, but it is possible to use ruby, python, or anything else you like, as long as it outputs a valid JSON string as the result.
|
|
||||||
|
|
||||||
_Make sure you have the `jq` command available, as we're using it to parse the JSON in the pre-hook script._
|
|
||||||
|
|
||||||
## Getting the IP address of the requester
|
|
||||||
<details>
|
|
||||||
<summary>script.sh</summary>
|
|
||||||
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
ip=$1
|
|
||||||
|
|
||||||
echo $ip >> ips.txt
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary>prehook.sh</summary>
|
|
||||||
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
context=$(cat)
|
|
||||||
ip=`echo $context | jq -r '.remoteAddr' | cut -d ':' -f 1`
|
|
||||||
|
|
||||||
echo "{\"ip\": \"$ip\"}"
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>hooks.json</summary>
|
|
||||||
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "log-ip",
|
|
||||||
"pre-hook-command": "/home/example/prehook.sh",
|
|
||||||
"execute-command": "/home/example/script.sh",
|
|
||||||
"pass-arguments-to-command": [
|
|
||||||
{ "source": "pre-hook", "name": "ip" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
</details>
|
|
@ -1,17 +1,7 @@
|
|||||||
# Referencing request values
|
# Referencing request values
|
||||||
There are four types of request values:
|
There are four types of request values:
|
||||||
|
|
||||||
1. Pre-hook values
|
1. HTTP Request Header values
|
||||||
These are the values provided by the `pre-hook-command` output. More details on the pre-hook command can be found [here](PreHook-Command.md).
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"source": "pre-hook",
|
|
||||||
"name": "parameter-name"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. HTTP Request Header values
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -20,7 +10,7 @@ There are four types of request values:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
3. HTTP Query parameters
|
2. HTTP Query parameters
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -29,6 +19,22 @@ There are four types of request values:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3. HTTP Request parameters
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"source": "request",
|
||||||
|
"name": "method"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"source": "request",
|
||||||
|
"name": "remote-addr"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
4. Payload (JSON or form-value encoded)
|
4. Payload (JSON or form-value encoded)
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -67,7 +73,7 @@ There are four types of request values:
|
|||||||
|
|
||||||
If the payload contains a key with the specified name "commits.0.commit.id", then the value of that key has priority over the dot-notation referencing.
|
If the payload contains a key with the specified name "commits.0.commit.id", then the value of that key has priority over the dot-notation referencing.
|
||||||
|
|
||||||
3. XML Payload
|
4. XML Payload
|
||||||
|
|
||||||
Referencing XML payload parameters is much like the JSON examples above, but XML is more complex.
|
Referencing XML payload parameters is much like the JSON examples above, but XML is more complex.
|
||||||
Element attributes are prefixed by a hyphen (`-`).
|
Element attributes are prefixed by a hyphen (`-`).
|
||||||
|
13
hooks.json
13
hooks.json
@ -1,13 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "test",
|
|
||||||
"pre-hook-command": "/home/adnan/test.sh",
|
|
||||||
"execute-command": "/bin/echo",
|
|
||||||
"pass-arguments-to-command": [
|
|
||||||
{
|
|
||||||
"source": "payload",
|
|
||||||
"name": "root"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
@ -17,9 +17,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -37,7 +35,8 @@ const (
|
|||||||
SourceQuery string = "url"
|
SourceQuery string = "url"
|
||||||
SourceQueryAlias string = "query"
|
SourceQueryAlias string = "query"
|
||||||
SourcePayload string = "payload"
|
SourcePayload string = "payload"
|
||||||
SourcePreHook string = "pre-hook"
|
SourceRawRequestBody string = "raw-request-body"
|
||||||
|
SourceRequest string = "request"
|
||||||
SourceString string = "string"
|
SourceString string = "string"
|
||||||
SourceEntirePayload string = "entire-payload"
|
SourceEntirePayload string = "entire-payload"
|
||||||
SourceEntireQuery string = "entire-query"
|
SourceEntireQuery string = "entire-query"
|
||||||
@ -97,6 +96,16 @@ func (e *SignatureError) Error() string {
|
|||||||
return fmt.Sprintf("invalid payload signature %s%s", e.Signature, empty)
|
return fmt.Sprintf("invalid payload signature %s%s", e.Signature, empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSignatureError returns whether err is of type SignatureError.
|
||||||
|
func IsSignatureError(err error) bool {
|
||||||
|
switch err.(type) {
|
||||||
|
case *SignatureError:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ArgumentError describes an invalid argument passed to Hook.
|
// ArgumentError describes an invalid argument passed to Hook.
|
||||||
type ArgumentError struct {
|
type ArgumentError struct {
|
||||||
Argument Argument
|
Argument Argument
|
||||||
@ -441,14 +450,33 @@ func (ha *Argument) Get(r *Request) (string, error) {
|
|||||||
case SourceHeader:
|
case SourceHeader:
|
||||||
source = &r.Headers
|
source = &r.Headers
|
||||||
key = textproto.CanonicalMIMEHeaderKey(ha.Name)
|
key = textproto.CanonicalMIMEHeaderKey(ha.Name)
|
||||||
|
|
||||||
case SourceQuery, SourceQueryAlias:
|
case SourceQuery, SourceQueryAlias:
|
||||||
source = &r.Query
|
source = &r.Query
|
||||||
|
|
||||||
case SourcePayload:
|
case SourcePayload:
|
||||||
source = &r.Payload
|
source = &r.Payload
|
||||||
case SourcePreHook:
|
|
||||||
source = &r.PreHook
|
|
||||||
case SourceString:
|
case SourceString:
|
||||||
return ha.Name, nil
|
return ha.Name, nil
|
||||||
|
|
||||||
|
case SourceRawRequestBody:
|
||||||
|
return string(r.Body), nil
|
||||||
|
|
||||||
|
case SourceRequest:
|
||||||
|
if r == nil || r.RawRequest == nil {
|
||||||
|
return "", errors.New("request is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(ha.Name) {
|
||||||
|
case "remote-addr":
|
||||||
|
return r.RawRequest.RemoteAddr, nil
|
||||||
|
case "method":
|
||||||
|
return r.RawRequest.Method, nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported request key: %q", ha.Name)
|
||||||
|
}
|
||||||
|
|
||||||
case SourceEntirePayload:
|
case SourceEntirePayload:
|
||||||
res, err := json.Marshal(&r.Payload)
|
res, err := json.Marshal(&r.Payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -456,6 +484,7 @@ func (ha *Argument) Get(r *Request) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return string(res), nil
|
return string(res), nil
|
||||||
|
|
||||||
case SourceEntireHeaders:
|
case SourceEntireHeaders:
|
||||||
res, err := json.Marshal(&r.Headers)
|
res, err := json.Marshal(&r.Headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -463,6 +492,7 @@ func (ha *Argument) Get(r *Request) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return string(res), nil
|
return string(res), nil
|
||||||
|
|
||||||
case SourceEntireQuery:
|
case SourceEntireQuery:
|
||||||
res, err := json.Marshal(&r.Query)
|
res, err := json.Marshal(&r.Query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -532,23 +562,10 @@ func (h *HooksFiles) Set(value string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PreHookContext is a structure consisted of request context data that will be passed to the pre-hook command
|
|
||||||
type PreHookContext struct {
|
|
||||||
HookID string `json:"hookID"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Base64EncodedBody string `json:"base64EncodedBody"`
|
|
||||||
RemoteAddr string `json:"remoteAddr"`
|
|
||||||
URI string `json:"URI"`
|
|
||||||
Host string `json:"host"`
|
|
||||||
Headers http.Header `json:"headers"`
|
|
||||||
Query url.Values `json:"query"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hook type is a structure containing details for a single hook
|
// Hook type is a structure containing details for a single hook
|
||||||
type Hook struct {
|
type Hook struct {
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
ExecuteCommand string `json:"execute-command,omitempty"`
|
ExecuteCommand string `json:"execute-command,omitempty"`
|
||||||
PreHookCommand string `json:"pre-hook-command,omitempty"`
|
|
||||||
CommandWorkingDirectory string `json:"command-working-directory,omitempty"`
|
CommandWorkingDirectory string `json:"command-working-directory,omitempty"`
|
||||||
ResponseMessage string `json:"response-message,omitempty"`
|
ResponseMessage string `json:"response-message,omitempty"`
|
||||||
ResponseHeaders ResponseHeaders `json:"response-headers,omitempty"`
|
ResponseHeaders ResponseHeaders `json:"response-headers,omitempty"`
|
||||||
@ -560,6 +577,7 @@ type Hook struct {
|
|||||||
JSONStringParameters []Argument `json:"parse-parameters-as-json,omitempty"`
|
JSONStringParameters []Argument `json:"parse-parameters-as-json,omitempty"`
|
||||||
TriggerRule *Rules `json:"trigger-rule,omitempty"`
|
TriggerRule *Rules `json:"trigger-rule,omitempty"`
|
||||||
TriggerRuleMismatchHttpResponseCode int `json:"trigger-rule-mismatch-http-response-code,omitempty"`
|
TriggerRuleMismatchHttpResponseCode int `json:"trigger-rule-mismatch-http-response-code,omitempty"`
|
||||||
|
TriggerSignatureSoftFailures bool `json:"trigger-signature-soft-failures,omitempty"`
|
||||||
IncomingPayloadContentType string `json:"incoming-payload-content-type,omitempty"`
|
IncomingPayloadContentType string `json:"incoming-payload-content-type,omitempty"`
|
||||||
SuccessHttpResponseCode int `json:"success-http-response-code,omitempty"`
|
SuccessHttpResponseCode int `json:"success-http-response-code,omitempty"`
|
||||||
HTTPMethods []string `json:"http-methods"`
|
HTTPMethods []string `json:"http-methods"`
|
||||||
@ -593,8 +611,6 @@ func (h *Hook) ParseJSONParameters(r *Request) []error {
|
|||||||
source = &r.Headers
|
source = &r.Headers
|
||||||
case SourcePayload:
|
case SourcePayload:
|
||||||
source = &r.Payload
|
source = &r.Payload
|
||||||
case SourcePreHook:
|
|
||||||
source = &r.PreHook
|
|
||||||
case SourceQuery, SourceQueryAlias:
|
case SourceQuery, SourceQueryAlias:
|
||||||
source = &r.Query
|
source = &r.Query
|
||||||
}
|
}
|
||||||
@ -844,9 +860,11 @@ func (r OrRule) Evaluate(req *Request) (bool, error) {
|
|||||||
rv, err := v.Evaluate(req)
|
rv, err := v.Evaluate(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !IsParameterNodeError(err) {
|
if !IsParameterNodeError(err) {
|
||||||
|
if !req.AllowSignatureErrors || (req.AllowSignatureErrors && !IsSignatureError(err)) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res = res || rv
|
res = res || rv
|
||||||
if res {
|
if res {
|
||||||
@ -895,7 +913,6 @@ func (r MatchRule) Evaluate(req *Request) (bool, error) {
|
|||||||
if r.Type == IPWhitelist {
|
if r.Type == IPWhitelist {
|
||||||
return CheckIPWhitelist(req.RawRequest.RemoteAddr, r.IPRange)
|
return CheckIPWhitelist(req.RawRequest.RemoteAddr, r.IPRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Type == ScalrSignature {
|
if r.Type == ScalrSignature {
|
||||||
return CheckScalrSignature(req, r.Secret, true)
|
return CheckScalrSignature(req, r.Secret, true)
|
||||||
}
|
}
|
||||||
|
@ -254,20 +254,21 @@ func TestExtractParameter(t *testing.T) {
|
|||||||
|
|
||||||
var argumentGetTests = []struct {
|
var argumentGetTests = []struct {
|
||||||
source, name string
|
source, name string
|
||||||
headers, query, payload, prehook map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
|
request *http.Request
|
||||||
value string
|
value string
|
||||||
ok bool
|
ok bool
|
||||||
}{
|
}{
|
||||||
{"header", "a", map[string]interface{}{"A": "z"}, nil, nil, nil, "z", true},
|
{"header", "a", map[string]interface{}{"A": "z"}, nil, nil, nil, "z", true},
|
||||||
{"url", "a", nil, map[string]interface{}{"a": "z"}, nil, nil, "z", true},
|
{"url", "a", nil, map[string]interface{}{"a": "z"}, nil, nil, "z", true},
|
||||||
{"payload", "a", nil, nil, map[string]interface{}{"a": "z"}, nil, "z", true},
|
{"payload", "a", nil, nil, map[string]interface{}{"a": "z"}, nil, "z", true},
|
||||||
{"prehook", "a", nil, nil, nil, map[string]interface{}{"a": "z"}, "z", true},
|
{"request", "METHOD", nil, nil, map[string]interface{}{"a": "z"}, &http.Request{Method: "POST", RemoteAddr: "127.0.0.1:1234"}, "POST", true},
|
||||||
{"string", "a", nil, nil, nil, nil, "a", true},
|
{"request", "remote-addr", nil, nil, map[string]interface{}{"a": "z"}, &http.Request{Method: "POST", RemoteAddr: "127.0.0.1:1234"}, "127.0.0.1:1234", true},
|
||||||
|
{"string", "a", nil, nil, map[string]interface{}{"a": "z"}, nil, "a", true},
|
||||||
// failures
|
// failures
|
||||||
{"header", "a", nil, map[string]interface{}{"a": "z"}, map[string]interface{}{"a": "z"}, nil, "", false}, // nil headers
|
{"header", "a", nil, map[string]interface{}{"a": "z"}, map[string]interface{}{"a": "z"}, nil, "", false}, // nil headers
|
||||||
{"url", "a", map[string]interface{}{"A": "z"}, nil, map[string]interface{}{"a": "z"}, nil, "", false}, // nil query
|
{"url", "a", map[string]interface{}{"A": "z"}, nil, map[string]interface{}{"a": "z"}, nil, "", false}, // nil query
|
||||||
{"payload", "a", map[string]interface{}{"A": "z"}, map[string]interface{}{"a": "z"}, nil, nil, "", false}, // nil payload
|
{"payload", "a", map[string]interface{}{"A": "z"}, map[string]interface{}{"a": "z"}, nil, nil, "", false}, // nil payload
|
||||||
{"prehook", "a", nil, nil, nil, nil, "", false}, // nil prehook
|
|
||||||
{"foo", "a", map[string]interface{}{"A": "z"}, nil, nil, nil, "", false}, // invalid source
|
{"foo", "a", map[string]interface{}{"A": "z"}, nil, nil, nil, "", false}, // invalid source
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,7 +279,7 @@ func TestArgumentGet(t *testing.T) {
|
|||||||
Headers: tt.headers,
|
Headers: tt.headers,
|
||||||
Query: tt.query,
|
Query: tt.query,
|
||||||
Payload: tt.payload,
|
Payload: tt.payload,
|
||||||
PreHook: tt.prehook,
|
RawRequest: tt.request,
|
||||||
}
|
}
|
||||||
value, err := a.Get(r)
|
value, err := a.Get(r)
|
||||||
if (err == nil) != tt.ok || value != tt.value {
|
if (err == nil) != tt.ok || value != tt.value {
|
||||||
@ -289,19 +290,18 @@ func TestArgumentGet(t *testing.T) {
|
|||||||
|
|
||||||
var hookParseJSONParametersTests = []struct {
|
var hookParseJSONParametersTests = []struct {
|
||||||
params []Argument
|
params []Argument
|
||||||
headers, query, payload, prehook map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
rheaders, rquery, rpayload, rprehook map[string]interface{}
|
rheaders, rquery, rpayload map[string]interface{}
|
||||||
ok bool
|
ok bool
|
||||||
}{
|
}{
|
||||||
{[]Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, nil, map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, nil, true},
|
{[]Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, true},
|
||||||
{[]Argument{Argument{"url", "a", "", false}}, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, nil, true},
|
{[]Argument{Argument{"url", "a", "", false}}, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true},
|
||||||
{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true},
|
{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true},
|
||||||
{[]Argument{Argument{"prehook", "a", "", false}}, nil, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true},
|
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": `{}`}, nil, nil, map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, true},
|
||||||
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": `{}`}, nil, nil, nil, map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, nil, true},
|
|
||||||
// failures
|
// failures
|
||||||
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, nil, false}, // empty string
|
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false}, // empty string
|
||||||
{[]Argument{Argument{"header", "y", "", false}}, map[string]interface{}{"X": `{}`}, nil, nil, nil, map[string]interface{}{"X": `{}`}, nil, nil, nil, false}, // missing parameter
|
{[]Argument{Argument{"header", "y", "", false}}, map[string]interface{}{"X": `{}`}, nil, nil, map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter
|
||||||
{[]Argument{Argument{"string", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, nil, false}, // invalid argument source
|
{[]Argument{Argument{"string", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false}, // invalid argument source
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHookParseJSONParameters(t *testing.T) {
|
func TestHookParseJSONParameters(t *testing.T) {
|
||||||
@ -311,7 +311,6 @@ func TestHookParseJSONParameters(t *testing.T) {
|
|||||||
Headers: tt.headers,
|
Headers: tt.headers,
|
||||||
Query: tt.query,
|
Query: tt.query,
|
||||||
Payload: tt.payload,
|
Payload: tt.payload,
|
||||||
PreHook: tt.prehook,
|
|
||||||
}
|
}
|
||||||
err := h.ParseJSONParameters(r)
|
err := h.ParseJSONParameters(r)
|
||||||
if (err == nil) != tt.ok || !reflect.DeepEqual(tt.headers, tt.rheaders) {
|
if (err == nil) != tt.ok || !reflect.DeepEqual(tt.headers, tt.rheaders) {
|
||||||
@ -323,13 +322,13 @@ func TestHookParseJSONParameters(t *testing.T) {
|
|||||||
var hookExtractCommandArgumentsTests = []struct {
|
var hookExtractCommandArgumentsTests = []struct {
|
||||||
exec string
|
exec string
|
||||||
args []Argument
|
args []Argument
|
||||||
headers, query, payload, prehook map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
value []string
|
value []string
|
||||||
ok bool
|
ok bool
|
||||||
}{
|
}{
|
||||||
{"test", []Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, nil, []string{"test", "z"}, true},
|
{"test", []Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"test", "z"}, true},
|
||||||
// failures
|
// failures
|
||||||
{"fail", []Argument{Argument{"payload", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, nil, []string{"fail", ""}, false},
|
{"fail", []Argument{Argument{"payload", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"fail", ""}, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHookExtractCommandArguments(t *testing.T) {
|
func TestHookExtractCommandArguments(t *testing.T) {
|
||||||
@ -339,7 +338,6 @@ func TestHookExtractCommandArguments(t *testing.T) {
|
|||||||
Headers: tt.headers,
|
Headers: tt.headers,
|
||||||
Query: tt.query,
|
Query: tt.query,
|
||||||
Payload: tt.payload,
|
Payload: tt.payload,
|
||||||
PreHook: tt.prehook,
|
|
||||||
}
|
}
|
||||||
value, err := h.ExtractCommandArguments(r)
|
value, err := h.ExtractCommandArguments(r)
|
||||||
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
||||||
@ -370,7 +368,7 @@ func TestHookExtractCommandArguments(t *testing.T) {
|
|||||||
var hookExtractCommandArgumentsForEnvTests = []struct {
|
var hookExtractCommandArgumentsForEnvTests = []struct {
|
||||||
exec string
|
exec string
|
||||||
args []Argument
|
args []Argument
|
||||||
headers, query, payload, prehook map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
value []string
|
value []string
|
||||||
ok bool
|
ok bool
|
||||||
}{
|
}{
|
||||||
@ -378,14 +376,14 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
|
|||||||
{
|
{
|
||||||
"test",
|
"test",
|
||||||
[]Argument{Argument{"header", "a", "", false}},
|
[]Argument{Argument{"header", "a", "", false}},
|
||||||
map[string]interface{}{"A": "z"}, nil, nil, nil,
|
map[string]interface{}{"A": "z"}, nil, nil,
|
||||||
[]string{"HOOK_a=z"},
|
[]string{"HOOK_a=z"},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"test",
|
"test",
|
||||||
[]Argument{Argument{"header", "a", "MYKEY", false}},
|
[]Argument{Argument{"header", "a", "MYKEY", false}},
|
||||||
map[string]interface{}{"A": "z"}, nil, nil, nil,
|
map[string]interface{}{"A": "z"}, nil, nil,
|
||||||
[]string{"MYKEY=z"},
|
[]string{"MYKEY=z"},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
@ -393,7 +391,7 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
|
|||||||
{
|
{
|
||||||
"fail",
|
"fail",
|
||||||
[]Argument{Argument{"payload", "a", "", false}},
|
[]Argument{Argument{"payload", "a", "", false}},
|
||||||
map[string]interface{}{"A": "z"}, nil, nil, nil,
|
map[string]interface{}{"A": "z"}, nil, nil,
|
||||||
[]string{},
|
[]string{},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
@ -406,7 +404,6 @@ func TestHookExtractCommandArgumentsForEnv(t *testing.T) {
|
|||||||
Headers: tt.headers,
|
Headers: tt.headers,
|
||||||
Query: tt.query,
|
Query: tt.query,
|
||||||
Payload: tt.payload,
|
Payload: tt.payload,
|
||||||
PreHook: tt.prehook,
|
|
||||||
}
|
}
|
||||||
value, err := h.ExtractCommandArgumentsForEnv(r)
|
value, err := h.ExtractCommandArgumentsForEnv(r)
|
||||||
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
||||||
@ -486,44 +483,43 @@ func TestHooksMatch(t *testing.T) {
|
|||||||
var matchRuleTests = []struct {
|
var matchRuleTests = []struct {
|
||||||
typ, regex, secret, value, ipRange string
|
typ, regex, secret, value, ipRange string
|
||||||
param Argument
|
param Argument
|
||||||
headers, query, payload, prehook map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
body []byte
|
body []byte
|
||||||
remoteAddr string
|
remoteAddr string
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
}{
|
}{
|
||||||
{"value", "", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", true, false},
|
{"value", "", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
|
||||||
{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", true, false},
|
{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
|
||||||
{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||||
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||||
{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||||
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||||
// failures
|
// failures
|
||||||
{"value", "", "", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", false, false},
|
{"value", "", "", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
||||||
{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", false, false},
|
{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
||||||
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"Y": "z"}, nil, nil, nil, []byte{}, "", false, true}, // reference invalid header
|
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header
|
||||||
// errors
|
// errors
|
||||||
{"regex", "*", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", false, true}, // invalid regex
|
{"regex", "*", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true}, // invalid regex
|
||||||
{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
{"payload-hmac-sha512", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
{"payload-hmac-sha512", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
{"payload-hash-sha512", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
{"payload-hash-sha512", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
|
|
||||||
// IP whitelisting, valid cases
|
// IP whitelisting, valid cases
|
||||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
||||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
||||||
{"ip-whitelist", "", "", "", "192.168.0.1", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.1:9000", true, false}, // valid IPv4, no range
|
{"ip-whitelist", "", "", "", "192.168.0.1", Argument{}, nil, nil, nil, []byte{}, "192.168.0.1:9000", true, false}, // valid IPv4, no range
|
||||||
{"ip-whitelist", "", "", "", "::1/24", Argument{}, nil, nil, nil, nil, []byte{}, "[::1]:9000", true, false}, // valid IPv6, with range
|
{"ip-whitelist", "", "", "", "::1/24", Argument{}, nil, nil, nil, []byte{}, "[::1]:9000", true, false}, // valid IPv6, with range
|
||||||
{"ip-whitelist", "", "", "", "::1", Argument{}, nil, nil, nil, nil, []byte{}, "[::1]:9000", true, false}, // valid IPv6, no range
|
{"ip-whitelist", "", "", "", "::1", Argument{}, nil, nil, nil, []byte{}, "[::1]:9000", true, false}, // valid IPv6, no range
|
||||||
// IP whitelisting, invalid cases
|
// IP whitelisting, invalid cases
|
||||||
{"ip-whitelist", "", "", "", "192.168.0.1/a", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.2:9000", false, true}, // invalid IPv4, with range
|
{"ip-whitelist", "", "", "", "192.168.0.1/a", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", false, true}, // invalid IPv4, with range
|
||||||
{"ip-whitelist", "", "", "", "192.168.0.a", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.2:9000", false, true}, // invalid IPv4, no range
|
{"ip-whitelist", "", "", "", "192.168.0.a", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", false, true}, // invalid IPv4, no range
|
||||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.a:9000", false, true}, // invalid IPv4 address
|
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.a:9000", false, true}, // invalid IPv4 address
|
||||||
{"ip-whitelist", "", "", "", "::1/a", Argument{}, nil, nil, nil, nil, []byte{}, "[::1]:9000", false, true}, // invalid IPv6, with range
|
{"ip-whitelist", "", "", "", "::1/a", Argument{}, nil, nil, nil, []byte{}, "[::1]:9000", false, true}, // invalid IPv6, with range
|
||||||
{"ip-whitelist", "", "", "", "::z", Argument{}, nil, nil, nil, nil, []byte{}, "[::1]:9000", false, true}, // invalid IPv6, no range
|
{"ip-whitelist", "", "", "", "::z", Argument{}, nil, nil, nil, []byte{}, "[::1]:9000", false, true}, // invalid IPv6, no range
|
||||||
{"ip-whitelist", "", "", "", "::1/24", Argument{}, nil, nil, nil, nil, []byte{}, "[::z]:9000", false, true}, // invalid IPv6 address
|
{"ip-whitelist", "", "", "", "::1/24", Argument{}, nil, nil, nil, []byte{}, "[::z]:9000", false, true}, // invalid IPv6 address
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMatchRule(t *testing.T) {
|
func TestMatchRule(t *testing.T) {
|
||||||
@ -533,7 +529,6 @@ func TestMatchRule(t *testing.T) {
|
|||||||
Headers: tt.headers,
|
Headers: tt.headers,
|
||||||
Query: tt.query,
|
Query: tt.query,
|
||||||
Payload: tt.payload,
|
Payload: tt.payload,
|
||||||
PreHook: tt.prehook,
|
|
||||||
Body: tt.body,
|
Body: tt.body,
|
||||||
RawRequest: &http.Request{
|
RawRequest: &http.Request{
|
||||||
RemoteAddr: tt.remoteAddr,
|
RemoteAddr: tt.remoteAddr,
|
||||||
@ -549,7 +544,7 @@ func TestMatchRule(t *testing.T) {
|
|||||||
var andRuleTests = []struct {
|
var andRuleTests = []struct {
|
||||||
desc string // description of the test case
|
desc string // description of the test case
|
||||||
rule AndRule
|
rule AndRule
|
||||||
headers, query, payload, prehook map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
body []byte
|
body []byte
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
@ -560,7 +555,8 @@ var andRuleTests = []struct {
|
|||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
map[string]interface{}{"A": "z", "B": "y"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"A": "z", "B": "y"}, nil, nil,
|
||||||
|
[]byte{},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -569,7 +565,8 @@ var andRuleTests = []struct {
|
|||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
map[string]interface{}{"A": "z", "B": "Y"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"A": "z", "B": "Y"}, nil, nil,
|
||||||
|
[]byte{},
|
||||||
false, false,
|
false, false,
|
||||||
},
|
},
|
||||||
// Complex test to cover Rules.Evaluate
|
// Complex test to cover Rules.Evaluate
|
||||||
@ -595,15 +592,16 @@ var andRuleTests = []struct {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
map[string]interface{}{"A": "z", "B": "y", "C": "x", "D": "w", "E": "X", "F": "X"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"A": "z", "B": "y", "C": "x", "D": "w", "E": "X", "F": "X"}, nil, nil,
|
||||||
|
[]byte{},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
{"empty rule", AndRule{{}}, nil, nil, nil, nil, nil, false, false},
|
{"empty rule", AndRule{{}}, nil, nil, nil, nil, false, false},
|
||||||
// failures
|
// failures
|
||||||
{
|
{
|
||||||
"invalid rule",
|
"invalid rule",
|
||||||
AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}},
|
AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}},
|
||||||
map[string]interface{}{"Y": "z"}, nil, nil, nil, nil,
|
map[string]interface{}{"Y": "z"}, nil, nil, nil,
|
||||||
false, true,
|
false, true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -614,7 +612,6 @@ func TestAndRule(t *testing.T) {
|
|||||||
Headers: tt.headers,
|
Headers: tt.headers,
|
||||||
Query: tt.query,
|
Query: tt.query,
|
||||||
Payload: tt.payload,
|
Payload: tt.payload,
|
||||||
PreHook: tt.prehook,
|
|
||||||
Body: tt.body,
|
Body: tt.body,
|
||||||
}
|
}
|
||||||
ok, err := tt.rule.Evaluate(r)
|
ok, err := tt.rule.Evaluate(r)
|
||||||
@ -627,7 +624,7 @@ func TestAndRule(t *testing.T) {
|
|||||||
var orRuleTests = []struct {
|
var orRuleTests = []struct {
|
||||||
desc string // description of the test case
|
desc string // description of the test case
|
||||||
rule OrRule
|
rule OrRule
|
||||||
headers, query, payload, prehook map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
body []byte
|
body []byte
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
@ -638,7 +635,8 @@ var orRuleTests = []struct {
|
|||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
map[string]interface{}{"A": "z", "B": "X"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"A": "z", "B": "X"}, nil, nil,
|
||||||
|
[]byte{},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -647,7 +645,8 @@ var orRuleTests = []struct {
|
|||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
map[string]interface{}{"A": "X", "B": "y"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"A": "X", "B": "y"}, nil, nil,
|
||||||
|
[]byte{},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -656,7 +655,8 @@ var orRuleTests = []struct {
|
|||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil,
|
||||||
|
[]byte{},
|
||||||
false, false,
|
false, false,
|
||||||
},
|
},
|
||||||
// failures
|
// failures
|
||||||
@ -665,7 +665,8 @@ var orRuleTests = []struct {
|
|||||||
OrRule{
|
OrRule{
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
},
|
},
|
||||||
map[string]interface{}{"Y": "Z"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"Y": "Z"}, nil, nil,
|
||||||
|
[]byte{},
|
||||||
false, false,
|
false, false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -676,7 +677,6 @@ func TestOrRule(t *testing.T) {
|
|||||||
Headers: tt.headers,
|
Headers: tt.headers,
|
||||||
Query: tt.query,
|
Query: tt.query,
|
||||||
Payload: tt.payload,
|
Payload: tt.payload,
|
||||||
PreHook: tt.prehook,
|
|
||||||
Body: tt.body,
|
Body: tt.body,
|
||||||
}
|
}
|
||||||
ok, err := tt.rule.Evaluate(r)
|
ok, err := tt.rule.Evaluate(r)
|
||||||
@ -689,13 +689,13 @@ func TestOrRule(t *testing.T) {
|
|||||||
var notRuleTests = []struct {
|
var notRuleTests = []struct {
|
||||||
desc string // description of the test case
|
desc string // description of the test case
|
||||||
rule NotRule
|
rule NotRule
|
||||||
headers, query, payload, prehook map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
body []byte
|
body []byte
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
}{
|
}{
|
||||||
{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, true, false},
|
{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
|
||||||
{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, false, false},
|
{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotRule(t *testing.T) {
|
func TestNotRule(t *testing.T) {
|
||||||
@ -704,7 +704,6 @@ func TestNotRule(t *testing.T) {
|
|||||||
Headers: tt.headers,
|
Headers: tt.headers,
|
||||||
Query: tt.query,
|
Query: tt.query,
|
||||||
Payload: tt.payload,
|
Payload: tt.payload,
|
||||||
PreHook: tt.prehook,
|
|
||||||
Body: tt.body,
|
Body: tt.body,
|
||||||
}
|
}
|
||||||
ok, err := tt.rule.Evaluate(r)
|
ok, err := tt.rule.Evaluate(r)
|
||||||
|
@ -31,11 +31,11 @@ type Request struct {
|
|||||||
// Payload is a map of the parsed payload.
|
// Payload is a map of the parsed payload.
|
||||||
Payload map[string]interface{}
|
Payload map[string]interface{}
|
||||||
|
|
||||||
// PreHook is a map of the parsed pre-hook command result
|
|
||||||
PreHook map[string]interface{}
|
|
||||||
|
|
||||||
// The underlying HTTP request.
|
// The underlying HTTP request.
|
||||||
RawRequest *http.Request
|
RawRequest *http.Request
|
||||||
|
|
||||||
|
// Treat signature errors as simple validate failures.
|
||||||
|
AllowSignatureErrors bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Request) ParseJSONPayload() error {
|
func (r *Request) ParseJSONPayload() error {
|
||||||
|
@ -55,6 +55,149 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "github-multi-sig",
|
||||||
|
"execute-command": "{{ .Hookecho }}",
|
||||||
|
"command-working-directory": "/",
|
||||||
|
"http-methods": ["Post "],
|
||||||
|
"include-command-output-in-response": true,
|
||||||
|
"trigger-rule-mismatch-http-response-code": 400,
|
||||||
|
"trigger-signature-soft-failures": true,
|
||||||
|
"pass-environment-to-command":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "head_commit.timestamp"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pass-arguments-to-command":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "head_commit.id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "head_commit.author.email"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trigger-rule":
|
||||||
|
{
|
||||||
|
"and":
|
||||||
|
[
|
||||||
|
"or":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"match":
|
||||||
|
{
|
||||||
|
"type": "payload-hmac-sha1",
|
||||||
|
"secret": "mysecretFAIL",
|
||||||
|
"parameter":
|
||||||
|
{
|
||||||
|
"source": "header",
|
||||||
|
"name": "X-Hub-Signature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match":
|
||||||
|
{
|
||||||
|
"type": "payload-hmac-sha1",
|
||||||
|
"secret": "mysecret",
|
||||||
|
"parameter":
|
||||||
|
{
|
||||||
|
"source": "header",
|
||||||
|
"name": "X-Hub-Signature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
"match":
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"value": "refs/heads/master",
|
||||||
|
"parameter":
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "ref"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "github-multi-sig-fail",
|
||||||
|
"execute-command": "{{ .Hookecho }}",
|
||||||
|
"command-working-directory": "/",
|
||||||
|
"http-methods": ["Post "],
|
||||||
|
"include-command-output-in-response": true,
|
||||||
|
"trigger-rule-mismatch-http-response-code": 400,
|
||||||
|
"pass-environment-to-command":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "head_commit.timestamp"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pass-arguments-to-command":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "head_commit.id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "head_commit.author.email"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trigger-rule":
|
||||||
|
{
|
||||||
|
"and":
|
||||||
|
[
|
||||||
|
"or":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"match":
|
||||||
|
{
|
||||||
|
"type": "payload-hmac-sha1",
|
||||||
|
"secret": "mysecretFAIL",
|
||||||
|
"parameter":
|
||||||
|
{
|
||||||
|
"source": "header",
|
||||||
|
"name": "X-Hub-Signature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match":
|
||||||
|
{
|
||||||
|
"type": "payload-hmac-sha1",
|
||||||
|
"secret": "mysecret",
|
||||||
|
"parameter":
|
||||||
|
{
|
||||||
|
"source": "header",
|
||||||
|
"name": "X-Hub-Signature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
"match":
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"value": "refs/heads/master",
|
||||||
|
"parameter":
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "ref"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "bitbucket",
|
"id": "bitbucket",
|
||||||
"execute-command": "{{ .Hookecho }}",
|
"execute-command": "{{ .Hookecho }}",
|
||||||
@ -168,6 +311,17 @@
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "txt-raw",
|
||||||
|
"execute-command": "{{ .Hookecho }}",
|
||||||
|
"command-working-directory": "/",
|
||||||
|
"include-command-output-in-response": true,
|
||||||
|
"pass-arguments-to-command": [
|
||||||
|
{
|
||||||
|
"source": "raw-request-body"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "sendgrid",
|
"id": "sendgrid",
|
||||||
"execute-command": "{{ .Hookecho }}",
|
"execute-command": "{{ .Hookecho }}",
|
||||||
@ -184,6 +338,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "sendgrid/dir",
|
||||||
|
"execute-command": "{{ .Hookecho }}",
|
||||||
|
"command-working-directory": "/",
|
||||||
|
"response-message": "success",
|
||||||
|
"trigger-rule": {
|
||||||
|
"match": {
|
||||||
|
"type": "value",
|
||||||
|
"parameter": {
|
||||||
|
"source": "payload",
|
||||||
|
"name": "root.0.event"
|
||||||
|
},
|
||||||
|
"value": "it worked!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "plex",
|
"id": "plex",
|
||||||
"execute-command": "{{ .Hookecho }}",
|
"execute-command": "{{ .Hookecho }}",
|
||||||
@ -252,6 +422,21 @@
|
|||||||
"include-command-output-in-response": true,
|
"include-command-output-in-response": true,
|
||||||
"include-command-output-in-response-on-error": true
|
"include-command-output-in-response-on-error": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "request-source",
|
||||||
|
"pass-arguments-to-command": [
|
||||||
|
{
|
||||||
|
"source": "request",
|
||||||
|
"name": "method"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "request",
|
||||||
|
"name": "remote-addr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"execute-command": "{{ .Hookecho }}",
|
||||||
|
"include-command-output-in-response": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "static-params-ok",
|
"id": "static-params-ok",
|
||||||
"execute-command": "{{ .Hookecho }}",
|
"execute-command": "{{ .Hookecho }}",
|
||||||
|
@ -28,6 +28,81 @@
|
|||||||
name: head_commit.timestamp
|
name: head_commit.timestamp
|
||||||
command-working-directory: /
|
command-working-directory: /
|
||||||
|
|
||||||
|
- id: github-multi-sig
|
||||||
|
http-methods:
|
||||||
|
- "Post "
|
||||||
|
trigger-rule:
|
||||||
|
and:
|
||||||
|
- or:
|
||||||
|
- match:
|
||||||
|
parameter:
|
||||||
|
source: header
|
||||||
|
name: X-Hub-Signature
|
||||||
|
secret: mysecretFAIL
|
||||||
|
type: payload-hmac-sha1
|
||||||
|
- match:
|
||||||
|
parameter:
|
||||||
|
source: header
|
||||||
|
name: X-Hub-Signature
|
||||||
|
secret: mysecret
|
||||||
|
type: payload-hmac-sha1
|
||||||
|
- match:
|
||||||
|
parameter:
|
||||||
|
source: payload
|
||||||
|
name: ref
|
||||||
|
type: value
|
||||||
|
value: refs/heads/master
|
||||||
|
include-command-output-in-response: true
|
||||||
|
trigger-rule-mismatch-http-response-code: 400
|
||||||
|
trigger-signature-soft-failures: true
|
||||||
|
execute-command: '{{ .Hookecho }}'
|
||||||
|
pass-arguments-to-command:
|
||||||
|
- source: payload
|
||||||
|
name: head_commit.id
|
||||||
|
- source: payload
|
||||||
|
name: head_commit.author.email
|
||||||
|
pass-environment-to-command:
|
||||||
|
- source: payload
|
||||||
|
name: head_commit.timestamp
|
||||||
|
command-working-directory: /
|
||||||
|
|
||||||
|
- id: github-multi-sig-fail
|
||||||
|
http-methods:
|
||||||
|
- "Post "
|
||||||
|
trigger-rule:
|
||||||
|
and:
|
||||||
|
- or:
|
||||||
|
- match:
|
||||||
|
parameter:
|
||||||
|
source: header
|
||||||
|
name: X-Hub-Signature
|
||||||
|
secret: mysecretFAIL
|
||||||
|
type: payload-hmac-sha1
|
||||||
|
- match:
|
||||||
|
parameter:
|
||||||
|
source: header
|
||||||
|
name: X-Hub-Signature
|
||||||
|
secret: mysecret
|
||||||
|
type: payload-hmac-sha1
|
||||||
|
- match:
|
||||||
|
parameter:
|
||||||
|
source: payload
|
||||||
|
name: ref
|
||||||
|
type: value
|
||||||
|
value: refs/heads/master
|
||||||
|
include-command-output-in-response: true
|
||||||
|
trigger-rule-mismatch-http-response-code: 400
|
||||||
|
execute-command: '{{ .Hookecho }}'
|
||||||
|
pass-arguments-to-command:
|
||||||
|
- source: payload
|
||||||
|
name: head_commit.id
|
||||||
|
- source: payload
|
||||||
|
name: head_commit.author.email
|
||||||
|
pass-environment-to-command:
|
||||||
|
- source: payload
|
||||||
|
name: head_commit.timestamp
|
||||||
|
command-working-directory: /
|
||||||
|
|
||||||
- id: bitbucket
|
- id: bitbucket
|
||||||
trigger-rule:
|
trigger-rule:
|
||||||
and:
|
and:
|
||||||
@ -97,6 +172,13 @@
|
|||||||
name: "app.messages.message.#text"
|
name: "app.messages.message.#text"
|
||||||
value: "Hello!!"
|
value: "Hello!!"
|
||||||
|
|
||||||
|
- id: txt-raw
|
||||||
|
execute-command: '{{ .Hookecho }}'
|
||||||
|
command-working-directory: /
|
||||||
|
include-command-output-in-response: true
|
||||||
|
pass-arguments-to-command:
|
||||||
|
- source: raw-request-body
|
||||||
|
|
||||||
- id: sendgrid
|
- id: sendgrid
|
||||||
execute-command: '{{ .Hookecho }}'
|
execute-command: '{{ .Hookecho }}'
|
||||||
command-working-directory: /
|
command-working-directory: /
|
||||||
@ -109,6 +191,18 @@
|
|||||||
name: root.0.event
|
name: root.0.event
|
||||||
value: processed
|
value: processed
|
||||||
|
|
||||||
|
- id: sendgrid/dir
|
||||||
|
execute-command: '{{ .Hookecho }}'
|
||||||
|
command-working-directory: /
|
||||||
|
response-message: success
|
||||||
|
trigger-rule:
|
||||||
|
match:
|
||||||
|
type: value
|
||||||
|
parameter:
|
||||||
|
source: payload
|
||||||
|
name: root.0.event
|
||||||
|
value: it worked!
|
||||||
|
|
||||||
- id: plex
|
- id: plex
|
||||||
trigger-rule:
|
trigger-rule:
|
||||||
match:
|
match:
|
||||||
@ -152,6 +246,15 @@
|
|||||||
include-command-output-in-response: true
|
include-command-output-in-response: true
|
||||||
include-command-output-in-response-on-error: true
|
include-command-output-in-response-on-error: true
|
||||||
|
|
||||||
|
- id: request-source
|
||||||
|
pass-arguments-to-command:
|
||||||
|
- source: request
|
||||||
|
name: method
|
||||||
|
- source: request
|
||||||
|
name: remote-addr
|
||||||
|
execute-command: '{{ .Hookecho }}'
|
||||||
|
include-command-output-in-response: true
|
||||||
|
|
||||||
- id: static-params-ok
|
- id: static-params-ok
|
||||||
execute-command: '{{ .Hookecho }}'
|
execute-command: '{{ .Hookecho }}'
|
||||||
include-command-output-in-response: true
|
include-command-output-in-response: true
|
||||||
|
136
webhook.go
136
webhook.go
@ -2,11 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@ -27,7 +25,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "2.7.0"
|
version = "2.8.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -264,7 +262,7 @@ func main() {
|
|||||||
// Clean up input
|
// Clean up input
|
||||||
*httpMethods = strings.ToUpper(strings.ReplaceAll(*httpMethods, " ", ""))
|
*httpMethods = strings.ToUpper(strings.ReplaceAll(*httpMethods, " ", ""))
|
||||||
|
|
||||||
hooksURL := makeURL(hooksURLPrefix)
|
hooksURL := makeRoutePattern(hooksURLPrefix)
|
||||||
|
|
||||||
r.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
r.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
fmt.Fprint(w, "OK")
|
fmt.Fprint(w, "OK")
|
||||||
@ -280,7 +278,7 @@ func main() {
|
|||||||
|
|
||||||
// Serve HTTP
|
// Serve HTTP
|
||||||
if !*secure {
|
if !*secure {
|
||||||
log.Printf("serving hooks on http://%s%s", addr, hooksURL)
|
log.Printf("serving hooks on http://%s%s", addr, makeHumanPattern(hooksURLPrefix))
|
||||||
log.Print(svr.Serve(ln))
|
log.Print(svr.Serve(ln))
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -295,7 +293,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
svr.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler)) // disable http/2
|
svr.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler)) // disable http/2
|
||||||
|
|
||||||
log.Printf("serving hooks on https://%s%s", addr, hooksURL)
|
log.Printf("serving hooks on https://%s%s", addr, makeHumanPattern(hooksURLPrefix))
|
||||||
log.Print(svr.ServeTLS(ln, *cert, *key))
|
log.Print(svr.ServeTLS(ln, *cert, *key))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,106 +469,6 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Printf("[%s] error parsing body payload due to unsupported content type header: %s\n", req.ID, req.ContentType)
|
log.Printf("[%s] error parsing body payload due to unsupported content type header: %s\n", req.ID, req.ContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if matchedHook.PreHookCommand != "" {
|
|
||||||
// check the command exists
|
|
||||||
var searchPath string
|
|
||||||
if filepath.IsAbs(matchedHook.PreHookCommand) || matchedHook.CommandWorkingDirectory == "" {
|
|
||||||
searchPath = matchedHook.PreHookCommand
|
|
||||||
} else {
|
|
||||||
searchPath = filepath.Join(matchedHook.CommandWorkingDirectory, matchedHook.PreHookCommand)
|
|
||||||
}
|
|
||||||
|
|
||||||
preHookCommandPath, err := exec.LookPath(searchPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[%s] unable to locate pre-hook command: '%s', %+v\n", req.ID, matchedHook.PreHookCommand, err)
|
|
||||||
// check if parameters specified in pre-hook command by mistake
|
|
||||||
if strings.IndexByte(matchedHook.PreHookCommand, ' ') != -1 {
|
|
||||||
s := strings.Fields(matchedHook.PreHookCommand)[0]
|
|
||||||
log.Printf("[%s] please use a wrapper script to provide arguments to pre-hook command for '%s'\n", req.ID, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
writeHttpResponseCode(w, req.ID, matchedHook.ID, http.StatusInternalServerError)
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
fmt.Fprint(w, "Error occurred while executing the hook's pre-hook command. Please check your logs for more details.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
preHookCommandStdin := hook.PreHookContext{
|
|
||||||
HookID: matchedHook.ID,
|
|
||||||
Method: r.Method,
|
|
||||||
Base64EncodedBody: base64.StdEncoding.EncodeToString(req.Body),
|
|
||||||
RemoteAddr: r.RemoteAddr,
|
|
||||||
URI: r.RequestURI,
|
|
||||||
Host: r.Host,
|
|
||||||
Headers: r.Header,
|
|
||||||
Query: r.URL.Query(),
|
|
||||||
}
|
|
||||||
|
|
||||||
preHookCommandStdinJSONString, err := json.Marshal(preHookCommandStdin)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[%s] unable to encode pre-hook context as JSON string for the pre-hook command: %+v\n", req.ID, err)
|
|
||||||
|
|
||||||
writeHttpResponseCode(w, req.ID, matchedHook.ID, http.StatusInternalServerError)
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
fmt.Fprint(w, "Error occurred while executing the hook's pre-hook command. Please check your logs for more details.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
preHookCommand := exec.Command(preHookCommandPath)
|
|
||||||
preHookCommand.Dir = matchedHook.CommandWorkingDirectory
|
|
||||||
preHookCommand.Env = append(os.Environ())
|
|
||||||
|
|
||||||
preHookCommandStdinPipe, err := preHookCommand.StdinPipe()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[%s] unable to acquire stdin pipe for the pre-hook command: %+v\n", req.ID, err)
|
|
||||||
|
|
||||||
writeHttpResponseCode(w, req.ID, matchedHook.ID, http.StatusInternalServerError)
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
fmt.Fprint(w, "Error occurred while executing the hook's pre-hook command. Please check your logs for more details.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.WriteString(preHookCommandStdinPipe, string(preHookCommandStdinJSONString))
|
|
||||||
|
|
||||||
preHookCommandStdinPipe.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[%s] unable to write to pre-hook command stdin: %+v\n", req.ID, err)
|
|
||||||
|
|
||||||
writeHttpResponseCode(w, req.ID, matchedHook.ID, http.StatusInternalServerError)
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
fmt.Fprint(w, "Error occurred while executing the hook's pre-hook command. Please check your logs for more details.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[%s] executing pre-hook command %s (%s) using %s as cwd\n", req.ID, matchedHook.PreHookCommand, preHookCommand.Path, preHookCommand.Dir)
|
|
||||||
|
|
||||||
preHookCommandOutput, err := preHookCommand.CombinedOutput()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[%s] unable to execute pre-hook command: %+v\n", req.ID, err)
|
|
||||||
|
|
||||||
writeHttpResponseCode(w, req.ID, matchedHook.ID, http.StatusInternalServerError)
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
fmt.Fprint(w, "Error occurred while executing the hook's pre-hook command. Please check your logs for more details.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
JSONDecoder := json.NewDecoder(strings.NewReader(string(preHookCommandOutput)))
|
|
||||||
JSONDecoder.UseNumber()
|
|
||||||
|
|
||||||
if err := JSONDecoder.Decode(&req.PreHook); err != nil {
|
|
||||||
log.Printf("[%s] unable to parse pre-hook command output: %+v\npre-hook command output was: %+v\n", req.ID, err, string(preHookCommandOutput))
|
|
||||||
|
|
||||||
writeHttpResponseCode(w, req.ID, matchedHook.ID, http.StatusInternalServerError)
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
fmt.Fprint(w, "Error occurred while executing the hook's pre-hook command. Please check your logs for more details.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle hook
|
// handle hook
|
||||||
errors := matchedHook.ParseJSONParameters(req)
|
errors := matchedHook.ParseJSONParameters(req)
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
@ -582,6 +480,9 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if matchedHook.TriggerRule == nil {
|
if matchedHook.TriggerRule == nil {
|
||||||
ok = true
|
ok = true
|
||||||
} else {
|
} else {
|
||||||
|
// Save signature soft failures option in request for evaluators
|
||||||
|
req.AllowSignatureErrors = matchedHook.TriggerSignatureSoftFailures
|
||||||
|
|
||||||
ok, err = matchedHook.TriggerRule.Evaluate(req)
|
ok, err = matchedHook.TriggerRule.Evaluate(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !hook.IsParameterNodeError(err) {
|
if !hook.IsParameterNodeError(err) {
|
||||||
@ -865,10 +766,21 @@ func valuesToMap(values map[string][]string) map[string]interface{} {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeURL builds a hook URL with or without a prefix.
|
// makeRoutePattern builds a pattern matching URL for the mux.
|
||||||
func makeURL(prefix *string) string {
|
func makeRoutePattern(prefix *string) string {
|
||||||
if prefix == nil || *prefix == "" {
|
return makeBaseURL(prefix) + "/{id:.*}"
|
||||||
return "/{id}"
|
}
|
||||||
}
|
|
||||||
return "/" + *prefix + "/{id}"
|
// makeHumanPattern builds a human-friendly URL for display.
|
||||||
|
func makeHumanPattern(prefix *string) string {
|
||||||
|
return makeBaseURL(prefix) + "/{id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeBaseURL creates the base URL before any mux pattern matching.
|
||||||
|
func makeBaseURL(prefix *string) string {
|
||||||
|
if prefix == nil || *prefix == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/" + *prefix
|
||||||
}
|
}
|
||||||
|
424
webhook_test.go
Normal file → Executable file
424
webhook_test.go
Normal file → Executable file
@ -33,7 +33,8 @@ func TestStaticParams(t *testing.T) {
|
|||||||
spHeaders["Accept"] = "*/*"
|
spHeaders["Accept"] = "*/*"
|
||||||
|
|
||||||
// case 2: binary with spaces in its name
|
// case 2: binary with spaces in its name
|
||||||
err := os.Symlink("/bin/echo", "/tmp/with space")
|
d1 := []byte("#!/bin/sh\n/bin/echo\n")
|
||||||
|
err := ioutil.WriteFile("/tmp/with space", d1, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%v", err)
|
t.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
@ -129,9 +130,21 @@ func TestWebhook(t *testing.T) {
|
|||||||
t.Errorf("POST %q: failed to ready body: %s", tt.desc, err)
|
t.Errorf("POST %q: failed to ready body: %s", tt.desc, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode != tt.respStatus || string(body) != tt.respBody {
|
// Test body
|
||||||
|
{
|
||||||
|
var bodyFailed bool
|
||||||
|
|
||||||
|
if tt.bodyIsRE {
|
||||||
|
bodyFailed = string(body) == tt.respBody
|
||||||
|
} else {
|
||||||
|
r := regexp.MustCompile(tt.respBody)
|
||||||
|
bodyFailed = !r.Match(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode != tt.respStatus || bodyFailed {
|
||||||
t.Errorf("failed %q (id: %s):\nexpected status: %#v, response: %s\ngot status: %#v, response: %s\ncommand output:\n%s\n", tt.desc, tt.id, tt.respStatus, tt.respBody, res.StatusCode, body, b)
|
t.Errorf("failed %q (id: %s):\nexpected status: %#v, response: %s\ngot status: %#v, response: %s\ncommand output:\n%s\n", tt.desc, tt.id, tt.respStatus, tt.respBody, res.StatusCode, body, b)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if tt.logMatch == "" {
|
if tt.logMatch == "" {
|
||||||
return
|
return
|
||||||
@ -303,6 +316,7 @@ var hookHandlerTests = []struct {
|
|||||||
headers map[string]string
|
headers map[string]string
|
||||||
contentType string
|
contentType string
|
||||||
body string
|
body string
|
||||||
|
bodyIsRE bool
|
||||||
|
|
||||||
respStatus int
|
respStatus int
|
||||||
respBody string
|
respBody string
|
||||||
@ -459,12 +473,327 @@ var hookHandlerTests = []struct {
|
|||||||
"watchers":1
|
"watchers":1
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
|
false,
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
||||||
env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
||||||
`,
|
`,
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"github-multi-sig",
|
||||||
|
"github-multi-sig",
|
||||||
|
nil,
|
||||||
|
"POST",
|
||||||
|
map[string]string{"X-Hub-Signature": "f68df0375d7b03e3eb29b4cf9f9ec12e08f42ff8"},
|
||||||
|
"application/json",
|
||||||
|
`{
|
||||||
|
"after":"1481a2de7b2a7d02428ad93446ab166be7793fbb",
|
||||||
|
"before":"17c497ccc7cca9c2f735aa07e9e3813060ce9a6a",
|
||||||
|
"commits":[
|
||||||
|
{
|
||||||
|
"added":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"author":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"committer":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"distinct":true,
|
||||||
|
"id":"c441029cf673f84c8b7db52d0a5944ee5c52ff89",
|
||||||
|
"message":"Test",
|
||||||
|
"modified":[
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"removed":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"timestamp":"2013-02-22T13:50:07-08:00",
|
||||||
|
"url":"https://github.com/octokitty/testing/commit/c441029cf673f84c8b7db52d0a5944ee5c52ff89"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"added":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"author":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"committer":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"distinct":true,
|
||||||
|
"id":"36c5f2243ed24de58284a96f2a643bed8c028658",
|
||||||
|
"message":"This is me testing the windows client.",
|
||||||
|
"modified":[
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"removed":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"timestamp":"2013-02-22T14:07:13-08:00",
|
||||||
|
"url":"https://github.com/octokitty/testing/commit/36c5f2243ed24de58284a96f2a643bed8c028658"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"added":[
|
||||||
|
"words/madame-bovary.txt"
|
||||||
|
],
|
||||||
|
"author":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"committer":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"distinct":true,
|
||||||
|
"id":"1481a2de7b2a7d02428ad93446ab166be7793fbb",
|
||||||
|
"message":"Rename madame-bovary.txt to words/madame-bovary.txt",
|
||||||
|
"modified":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"removed":[
|
||||||
|
"madame-bovary.txt"
|
||||||
|
],
|
||||||
|
"timestamp":"2013-03-12T08:14:29-07:00",
|
||||||
|
"url":"https://github.com/octokitty/testing/commit/1481a2de7b2a7d02428ad93446ab166be7793fbb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compare":"https://github.com/octokitty/testing/compare/17c497ccc7cc...1481a2de7b2a",
|
||||||
|
"created":false,
|
||||||
|
"deleted":false,
|
||||||
|
"forced":false,
|
||||||
|
"head_commit":{
|
||||||
|
"added":[
|
||||||
|
"words/madame-bovary.txt"
|
||||||
|
],
|
||||||
|
"author":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"committer":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"distinct":true,
|
||||||
|
"id":"1481a2de7b2a7d02428ad93446ab166be7793fbb",
|
||||||
|
"message":"Rename madame-bovary.txt to words/madame-bovary.txt",
|
||||||
|
"modified":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"removed":[
|
||||||
|
"madame-bovary.txt"
|
||||||
|
],
|
||||||
|
"timestamp":"2013-03-12T08:14:29-07:00",
|
||||||
|
"url":"https://github.com/octokitty/testing/commit/1481a2de7b2a7d02428ad93446ab166be7793fbb"
|
||||||
|
},
|
||||||
|
"pusher":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian"
|
||||||
|
},
|
||||||
|
"ref":"refs/heads/master",
|
||||||
|
"repository":{
|
||||||
|
"created_at":1332977768,
|
||||||
|
"description":"",
|
||||||
|
"fork":false,
|
||||||
|
"forks":0,
|
||||||
|
"has_downloads":true,
|
||||||
|
"has_issues":true,
|
||||||
|
"has_wiki":true,
|
||||||
|
"homepage":"",
|
||||||
|
"id":3860742,
|
||||||
|
"language":"Ruby",
|
||||||
|
"master_branch":"master",
|
||||||
|
"name":"testing",
|
||||||
|
"open_issues":2,
|
||||||
|
"owner":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"octokitty"
|
||||||
|
},
|
||||||
|
"private":false,
|
||||||
|
"pushed_at":1363295520,
|
||||||
|
"size":2156,
|
||||||
|
"stargazers":1,
|
||||||
|
"url":"https://github.com/octokitty/testing",
|
||||||
|
"watchers":1
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
http.StatusOK,
|
||||||
|
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
||||||
|
env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
||||||
|
`,
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"github-multi-sig-fail",
|
||||||
|
"github-multi-sig-fail",
|
||||||
|
nil,
|
||||||
|
"POST",
|
||||||
|
map[string]string{"X-Hub-Signature": "f68df0375d7b03e3eb29b4cf9f9ec12e08f42ff8"},
|
||||||
|
"application/json",
|
||||||
|
`{
|
||||||
|
"after":"1481a2de7b2a7d02428ad93446ab166be7793fbb",
|
||||||
|
"before":"17c497ccc7cca9c2f735aa07e9e3813060ce9a6a",
|
||||||
|
"commits":[
|
||||||
|
{
|
||||||
|
"added":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"author":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"committer":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"distinct":true,
|
||||||
|
"id":"c441029cf673f84c8b7db52d0a5944ee5c52ff89",
|
||||||
|
"message":"Test",
|
||||||
|
"modified":[
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"removed":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"timestamp":"2013-02-22T13:50:07-08:00",
|
||||||
|
"url":"https://github.com/octokitty/testing/commit/c441029cf673f84c8b7db52d0a5944ee5c52ff89"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"added":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"author":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"committer":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"distinct":true,
|
||||||
|
"id":"36c5f2243ed24de58284a96f2a643bed8c028658",
|
||||||
|
"message":"This is me testing the windows client.",
|
||||||
|
"modified":[
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"removed":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"timestamp":"2013-02-22T14:07:13-08:00",
|
||||||
|
"url":"https://github.com/octokitty/testing/commit/36c5f2243ed24de58284a96f2a643bed8c028658"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"added":[
|
||||||
|
"words/madame-bovary.txt"
|
||||||
|
],
|
||||||
|
"author":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"committer":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"distinct":true,
|
||||||
|
"id":"1481a2de7b2a7d02428ad93446ab166be7793fbb",
|
||||||
|
"message":"Rename madame-bovary.txt to words/madame-bovary.txt",
|
||||||
|
"modified":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"removed":[
|
||||||
|
"madame-bovary.txt"
|
||||||
|
],
|
||||||
|
"timestamp":"2013-03-12T08:14:29-07:00",
|
||||||
|
"url":"https://github.com/octokitty/testing/commit/1481a2de7b2a7d02428ad93446ab166be7793fbb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compare":"https://github.com/octokitty/testing/compare/17c497ccc7cc...1481a2de7b2a",
|
||||||
|
"created":false,
|
||||||
|
"deleted":false,
|
||||||
|
"forced":false,
|
||||||
|
"head_commit":{
|
||||||
|
"added":[
|
||||||
|
"words/madame-bovary.txt"
|
||||||
|
],
|
||||||
|
"author":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"committer":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian",
|
||||||
|
"username":"octokitty"
|
||||||
|
},
|
||||||
|
"distinct":true,
|
||||||
|
"id":"1481a2de7b2a7d02428ad93446ab166be7793fbb",
|
||||||
|
"message":"Rename madame-bovary.txt to words/madame-bovary.txt",
|
||||||
|
"modified":[
|
||||||
|
|
||||||
|
],
|
||||||
|
"removed":[
|
||||||
|
"madame-bovary.txt"
|
||||||
|
],
|
||||||
|
"timestamp":"2013-03-12T08:14:29-07:00",
|
||||||
|
"url":"https://github.com/octokitty/testing/commit/1481a2de7b2a7d02428ad93446ab166be7793fbb"
|
||||||
|
},
|
||||||
|
"pusher":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"Garen Torikian"
|
||||||
|
},
|
||||||
|
"ref":"refs/heads/master",
|
||||||
|
"repository":{
|
||||||
|
"created_at":1332977768,
|
||||||
|
"description":"",
|
||||||
|
"fork":false,
|
||||||
|
"forks":0,
|
||||||
|
"has_downloads":true,
|
||||||
|
"has_issues":true,
|
||||||
|
"has_wiki":true,
|
||||||
|
"homepage":"",
|
||||||
|
"id":3860742,
|
||||||
|
"language":"Ruby",
|
||||||
|
"master_branch":"master",
|
||||||
|
"name":"testing",
|
||||||
|
"open_issues":2,
|
||||||
|
"owner":{
|
||||||
|
"email":"lolwut@noway.biz",
|
||||||
|
"name":"octokitty"
|
||||||
|
},
|
||||||
|
"private":false,
|
||||||
|
"pushed_at":1363295520,
|
||||||
|
"size":2156,
|
||||||
|
"stargazers":1,
|
||||||
|
"url":"https://github.com/octokitty/testing",
|
||||||
|
"watchers":1
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
`Error occurred while evaluating hook rules.`,
|
||||||
|
``,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"bitbucket", // bitbucket sends their payload using uriencoded params.
|
"bitbucket", // bitbucket sends their payload using uriencoded params.
|
||||||
"bitbucket",
|
"bitbucket",
|
||||||
@ -473,6 +802,7 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
nil,
|
nil,
|
||||||
"application/x-www-form-urlencoded",
|
"application/x-www-form-urlencoded",
|
||||||
`payload={"canon_url": "https://bitbucket.org","commits": [{"author": "marcus","branch": "master","files": [{"file": "somefile.py","type": "modified"}],"message": "Added some more things to somefile.py\n","node": "620ade18607a","parents": ["702c70160afc"],"raw_author": "Marcus Bertrand <marcus@somedomain.com>","raw_node": "620ade18607ac42d872b568bb92acaa9a28620e9","revision": null,"size": -1,"timestamp": "2012-05-30 05:58:56","utctimestamp": "2014-11-07 15:19:02+00:00"}],"repository": {"absolute_url": "/webhook/testing/","fork": false,"is_private": true,"name": "Project X","owner": "marcus","scm": "git","slug": "project-x","website": "https://atlassian.com/"},"user": "marcus"}`,
|
`payload={"canon_url": "https://bitbucket.org","commits": [{"author": "marcus","branch": "master","files": [{"file": "somefile.py","type": "modified"}],"message": "Added some more things to somefile.py\n","node": "620ade18607a","parents": ["702c70160afc"],"raw_author": "Marcus Bertrand <marcus@somedomain.com>","raw_node": "620ade18607ac42d872b568bb92acaa9a28620e9","revision": null,"size": -1,"timestamp": "2012-05-30 05:58:56","utctimestamp": "2014-11-07 15:19:02+00:00"}],"repository": {"absolute_url": "/webhook/testing/","fork": false,"is_private": true,"name": "Project X","owner": "marcus","scm": "git","slug": "project-x","website": "https://atlassian.com/"},"user": "marcus"}`,
|
||||||
|
false,
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`success`,
|
`success`,
|
||||||
``,
|
``,
|
||||||
@ -526,6 +856,7 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
],
|
],
|
||||||
"total_commits_count": 4
|
"total_commits_count": 4
|
||||||
}`,
|
}`,
|
||||||
|
false,
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`arg: b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327 John Smith john@example.com
|
`arg: b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327 John Smith john@example.com
|
||||||
`,
|
`,
|
||||||
@ -547,10 +878,30 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
<message id="1" from_user="1" to_user="2">Hello!!</message>
|
<message id="1" from_user="1" to_user="2">Hello!!</message>
|
||||||
</messages>
|
</messages>
|
||||||
</app>`,
|
</app>`,
|
||||||
|
false,
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`success`,
|
`success`,
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"txt-raw",
|
||||||
|
"txt-raw",
|
||||||
|
nil,
|
||||||
|
"POST",
|
||||||
|
map[string]string{"Content-Type": "text/plain"},
|
||||||
|
"text/plain",
|
||||||
|
`# FOO
|
||||||
|
|
||||||
|
blah
|
||||||
|
blah`,
|
||||||
|
false,
|
||||||
|
http.StatusOK,
|
||||||
|
`# FOO
|
||||||
|
|
||||||
|
blah
|
||||||
|
blah`,
|
||||||
|
``,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"payload-json-array",
|
"payload-json-array",
|
||||||
"sendgrid",
|
"sendgrid",
|
||||||
@ -569,6 +920,30 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
"sg_message_id": "sg_message_id"
|
"sg_message_id": "sg_message_id"
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
|
false,
|
||||||
|
http.StatusOK,
|
||||||
|
`success`,
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slash-in-hook-id",
|
||||||
|
"sendgrid/dir",
|
||||||
|
nil,
|
||||||
|
"POST",
|
||||||
|
nil,
|
||||||
|
"application/json",
|
||||||
|
`[
|
||||||
|
{
|
||||||
|
"email": "example@test.com",
|
||||||
|
"timestamp": 1513299569,
|
||||||
|
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>",
|
||||||
|
"event": "it worked!",
|
||||||
|
"category": "cat facts",
|
||||||
|
"sg_event_id": "sg_event_id",
|
||||||
|
"sg_message_id": "sg_message_id"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
false,
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`success`,
|
`success`,
|
||||||
``,
|
``,
|
||||||
@ -601,6 +976,7 @@ Content-Transfer-Encoding: binary
|
|||||||
|
|
||||||
binary data
|
binary data
|
||||||
--xxx--`,
|
--xxx--`,
|
||||||
|
false,
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`success`,
|
`success`,
|
||||||
``,
|
``,
|
||||||
@ -614,6 +990,7 @@ binary data
|
|||||||
nil,
|
nil,
|
||||||
"application/json",
|
"application/json",
|
||||||
`{"exists": 1}`,
|
`{"exists": 1}`,
|
||||||
|
false,
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`success`,
|
`success`,
|
||||||
``,
|
``,
|
||||||
@ -627,6 +1004,7 @@ binary data
|
|||||||
nil,
|
nil,
|
||||||
"application/json",
|
"application/json",
|
||||||
`{"exists": 1}`,
|
`{"exists": 1}`,
|
||||||
|
false,
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`Hook rules were not satisfied.`,
|
`Hook rules were not satisfied.`,
|
||||||
`parameter node not found`,
|
`parameter node not found`,
|
||||||
@ -668,6 +1046,7 @@ binary data
|
|||||||
},
|
},
|
||||||
"ref":"refs/heads/master"
|
"ref":"refs/heads/master"
|
||||||
}`,
|
}`,
|
||||||
|
false,
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
||||||
env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
||||||
@ -710,6 +1089,7 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
},
|
},
|
||||||
"ref":"refs/heads/master"
|
"ref":"refs/heads/master"
|
||||||
}`,
|
}`,
|
||||||
|
false,
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
||||||
`,
|
`,
|
||||||
@ -724,34 +1104,50 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
map[string]string{"X-Hub-Signature": "33f9d709782f62b8b4a0178586c65ab098a39fe2"},
|
map[string]string{"X-Hub-Signature": "33f9d709782f62b8b4a0178586c65ab098a39fe2"},
|
||||||
"application/json",
|
"application/json",
|
||||||
``,
|
``,
|
||||||
|
false,
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
``,
|
``,
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"request-source",
|
||||||
|
"request-source",
|
||||||
|
nil,
|
||||||
|
"POST",
|
||||||
|
map[string]string{"X-Hub-Signature": "33f9d709782f62b8b4a0178586c65ab098a39fe2"},
|
||||||
|
"application/json",
|
||||||
|
`{}`,
|
||||||
|
true,
|
||||||
|
http.StatusOK,
|
||||||
|
`arg: POST 127.0.0.1:.*
|
||||||
|
`,
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
|
||||||
// test with disallowed global HTTP method
|
// test with disallowed global HTTP method
|
||||||
{"global disallowed method", "bitbucket", []string{"Post "}, "GET", nil, `{}`, "application/json", http.StatusMethodNotAllowed, ``, ``},
|
{"global disallowed method", "bitbucket", []string{"Post "}, "GET", nil, `{}`, "application/json", false, http.StatusMethodNotAllowed, ``, ``},
|
||||||
// test with disallowed HTTP method
|
// test with disallowed HTTP method
|
||||||
{"disallowed method", "github", nil, "Get", nil, `{}`, "application/json", http.StatusMethodNotAllowed, ``, ``},
|
{"disallowed method", "github", nil, "Get", nil, `{}`, "application/json", false, http.StatusMethodNotAllowed, ``, ``},
|
||||||
// test with custom return code
|
// test with custom return code
|
||||||
{"empty payload", "github", nil, "POST", nil, "application/json", `{}`, http.StatusBadRequest, `Hook rules were not satisfied.`, ``},
|
{"empty payload", "github", nil, "POST", nil, "application/json", `{}`, false, http.StatusBadRequest, `Hook rules were not satisfied.`, ``},
|
||||||
// test with custom invalid http code, should default to 200 OK
|
// test with custom invalid http code, should default to 200 OK
|
||||||
{"empty payload", "bitbucket", nil, "POST", nil, "application/json", `{}`, http.StatusOK, `Hook rules were not satisfied.`, ``},
|
{"empty payload", "bitbucket", nil, "POST", nil, "application/json", `{}`, false, http.StatusOK, `Hook rules were not satisfied.`, ``},
|
||||||
// test with no configured http return code, should default to 200 OK
|
// test with no configured http return code, should default to 200 OK
|
||||||
{"empty payload", "gitlab", nil, "POST", nil, "application/json", `{}`, http.StatusOK, `Hook rules were not satisfied.`, ``},
|
{"empty payload", "gitlab", nil, "POST", nil, "application/json", `{}`, false, http.StatusOK, `Hook rules were not satisfied.`, ``},
|
||||||
|
|
||||||
// test capturing command output
|
// test capturing command output
|
||||||
{"don't capture output on success by default", "capture-command-output-on-success-not-by-default", nil, "POST", nil, "application/json", `{}`, http.StatusOK, ``, ``},
|
{"don't capture output on success by default", "capture-command-output-on-success-not-by-default", nil, "POST", nil, "application/json", `{}`, false, http.StatusOK, ``, ``},
|
||||||
{"capture output on success with flag set", "capture-command-output-on-success-yes-with-flag", nil, "POST", nil, "application/json", `{}`, http.StatusOK, `arg: exit=0
|
{"capture output on success with flag set", "capture-command-output-on-success-yes-with-flag", nil, "POST", nil, "application/json", `{}`, false, http.StatusOK, `arg: exit=0
|
||||||
`, ``},
|
`, ``},
|
||||||
{"don't capture output on error by default", "capture-command-output-on-error-not-by-default", nil, "POST", nil, "application/json", `{}`, http.StatusInternalServerError, `Error occurred while executing the hook's command. Please check your logs for more details.`, ``},
|
{"don't capture output on error by default", "capture-command-output-on-error-not-by-default", nil, "POST", nil, "application/json", `{}`, false, http.StatusInternalServerError, `Error occurred while executing the hook's command. Please check your logs for more details.`, ``},
|
||||||
{"capture output on error with extra flag set", "capture-command-output-on-error-yes-with-extra-flag", nil, "POST", nil, "application/json", `{}`, http.StatusInternalServerError, `arg: exit=1
|
{"capture output on error with extra flag set", "capture-command-output-on-error-yes-with-extra-flag", nil, "POST", nil, "application/json", `{}`, false, http.StatusInternalServerError, `arg: exit=1
|
||||||
`, ``},
|
`, ``},
|
||||||
|
|
||||||
// Check logs
|
// Check logs
|
||||||
{"static params should pass", "static-params-ok", nil, "POST", nil, "application/json", `{}`, http.StatusOK, "arg: passed\n", `(?s)command output: arg: passed`},
|
{"static params should pass", "static-params-ok", nil, "POST", nil, "application/json", `{}`, false, http.StatusOK, "arg: passed\n", `(?s)command output: arg: passed`},
|
||||||
{"command with space logs warning", "warn-on-space", nil, "POST", nil, "application/json", `{}`, http.StatusInternalServerError, "Error occurred while executing the hook's command. Please check your logs for more details.", `(?s)error in exec:.*use 'pass[-]arguments[-]to[-]command' to specify args`},
|
{"command with space logs warning", "warn-on-space", nil, "POST", nil, "application/json", `{}`, false, http.StatusInternalServerError, "Error occurred while executing the hook's command. Please check your logs for more details.", `(?s)error in exec:.*use 'pass[-]arguments[-]to[-]command' to specify args`},
|
||||||
{"unsupported content type error", "github", nil, "POST", map[string]string{"Content-Type": "nonexistent/format"}, "application/json", `{}`, http.StatusBadRequest, `Hook rules were not satisfied.`, `(?s)error parsing body payload due to unsupported content type header:`},
|
{"unsupported content type error", "github", nil, "POST", map[string]string{"Content-Type": "nonexistent/format"}, "application/json", `{}`, false, http.StatusBadRequest, `Hook rules were not satisfied.`, `(?s)error parsing body payload due to unsupported content type header:`},
|
||||||
}
|
}
|
||||||
|
|
||||||
// buffer provides a concurrency-safe bytes.Buffer to tests above.
|
// buffer provides a concurrency-safe bytes.Buffer to tests above.
|
||||||
|
Loading…
Reference in New Issue
Block a user