-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #38 from thomae/webhook
Add generic Webhook (HTTP POST callback) as notifier
- Loading branch information
Showing
6 changed files
with
239 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
0.4.3 | ||
0.4.4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,14 @@ to="" | |
enabled=false | ||
webhookURL="" | ||
|
||
[webhook] | ||
enabled=false | ||
webhook_url="https://webhook.somewhere.com/webhook" | ||
webhook_url_all_clear="https://webhook.somewhere.com/webhook" | ||
webhook_secret="" | ||
request_timeout="10s" | ||
allow_insecure_tls=false | ||
|
||
[xmpp] | ||
enabled=false | ||
to=["[email protected]"] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package notifier | ||
|
||
import ( | ||
"bytes" | ||
"crypto/hmac" | ||
"crypto/sha256" | ||
"crypto/tls" | ||
"encoding/hex" | ||
"encoding/json" | ||
"net/http" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
type webhookNotifier struct { | ||
WebhookURL string | ||
WebhookURLAllClear string | ||
WebhookSecret string | ||
httpClient *http.Client | ||
} | ||
|
||
func ComputeHmacSha256(secret string, payload []byte) string { | ||
key := []byte(secret) | ||
h := hmac.New(sha256.New, key) | ||
h.Write([]byte(payload)) | ||
return hex.EncodeToString(h.Sum(nil)) | ||
} | ||
|
||
// NewWebhook creates a new webhook notifier from the supplied configuration. | ||
func NewWebhook(WebhookURL string, | ||
WebhookURLAllClear string, | ||
WebhookSecret string, | ||
RequestTimeout time.Duration, | ||
AllowInsecureTLS bool) (Notifier, error) { | ||
|
||
if WebhookURL == "" { | ||
return nil, errors.New("Unable to initialize webhook: webhookURL is empty") | ||
} | ||
if WebhookURLAllClear == "" { | ||
return nil, errors.New("Unable to initialize webhook: webhookURL_all_clear is empty") | ||
} | ||
|
||
httpClient := &http.Client{Timeout: time.Second * RequestTimeout} | ||
if AllowInsecureTLS { | ||
transport := &http.Transport{ | ||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, | ||
} | ||
httpClient = &http.Client{Transport: transport, Timeout: time.Second * RequestTimeout} | ||
} | ||
|
||
return &webhookNotifier{ | ||
WebhookURL, | ||
WebhookURLAllClear, | ||
WebhookSecret, | ||
httpClient, | ||
}, nil | ||
} | ||
|
||
// Notify implements the Notifier interface for webhook. | ||
func (w *webhookNotifier) Notify(msg Message) error { | ||
postBody, _ := json.Marshal(map[string]interface{}{ | ||
"message": msg.Format(), | ||
"meta": msg.Meta, | ||
}) | ||
request, err := http.NewRequest("POST", w.WebhookURL, bytes.NewBuffer(postBody)) | ||
request.Header.Set("Content-Type", "application/json") | ||
request.Header.Set("X-Program", msg.Program) | ||
|
||
if w.WebhookSecret != "" { | ||
timestamp := strconv.FormatInt(time.Now().Unix(), 10) | ||
payload := append([]byte(timestamp), postBody...) | ||
signature := ComputeHmacSha256(w.WebhookSecret, payload) | ||
|
||
request.Header.Set("X-Timestamp", timestamp) | ||
request.Header.Set("X-HMAC-SHA256", signature) | ||
} | ||
|
||
_, err = w.httpClient.Do(request) | ||
if err != nil { | ||
return errors.Wrap(err, "unable to notify via webhook") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// NotifyAllClear implements the Notifier interface for webhook. | ||
func (w *webhookNotifier) NotifyAllClear(msg Message) error { | ||
postBody, _ := json.Marshal(map[string]interface{}{ | ||
"message": msg.FormatAllClear(), | ||
"meta": msg.Meta, | ||
}) | ||
request, err := http.NewRequest("POST", w.WebhookURLAllClear, bytes.NewBuffer(postBody)) | ||
request.Header.Set("Content-Type", "application/json") | ||
request.Header.Set("X-Program", msg.Program) | ||
|
||
if w.WebhookSecret != "" { | ||
timestamp := strconv.FormatInt(time.Now().Unix(), 10) | ||
payload := append([]byte(timestamp), postBody...) | ||
signature := ComputeHmacSha256(w.WebhookSecret, payload) | ||
|
||
request.Header.Set("X-Timestamp", timestamp) | ||
request.Header.Set("X-HMAC-SHA256", signature) | ||
} | ||
|
||
_, err = w.httpClient.Do(request) | ||
if err != nil { | ||
return errors.Wrap(err, "unable to notify via webhook") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (w *webhookNotifier) String() string { | ||
return "webhook" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package main | ||
|
||
import ( | ||
"crypto/hmac" | ||
"crypto/sha256" | ||
"encoding/hex" | ||
"io/ioutil" | ||
"log" | ||
"math" | ||
"net/http" | ||
"strconv" | ||
"time" | ||
) | ||
|
||
func ComputeHmacSha256(secret string, message []byte) string { | ||
key := []byte(secret) | ||
h := hmac.New(sha256.New, key) | ||
h.Write(message) | ||
return hex.EncodeToString(h.Sum(nil)) | ||
} | ||
|
||
func main() { | ||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||
webhookSecret := "" | ||
|
||
if r.Method != http.MethodPost { | ||
http.Error(w, "Method Not Allowed", 405) | ||
return | ||
} | ||
|
||
contentType := r.Header.Get("Content-Type") | ||
if contentType != "application/json" { | ||
http.Error(w, "Unsupported Media Type", 415) | ||
return | ||
} | ||
|
||
sendingProgram := r.Header.Get("X-Program") | ||
log.Println("X-Program: " + sendingProgram) | ||
|
||
currentTimestamp := strconv.FormatInt(time.Now().Unix(), 10) | ||
requestTimestamp := r.Header.Get("X-Timestamp") | ||
hmacSHA256 := r.Header.Get("X-HMAC-SHA256") | ||
|
||
body, err := ioutil.ReadAll(r.Body) | ||
defer r.Body.Close() | ||
if err != nil { | ||
http.Error(w, "Internal Server Error", 500) | ||
return | ||
} | ||
|
||
if requestTimestamp != "" && hmacSHA256 != "" { | ||
payload := append([]byte(requestTimestamp), body...) | ||
signature := ComputeHmacSha256(webhookSecret, payload) | ||
log.Println("X-Timestamp: " + requestTimestamp) | ||
log.Println("Current timestamp: " + currentTimestamp) | ||
log.Println("X-HMAC-SHA256: " + hmacSHA256) | ||
log.Println("Calculated signature: " + signature) | ||
if hmacSHA256 == signature { | ||
log.Println("Signature is correct") | ||
} else { | ||
log.Println("Signature is not correct") | ||
http.Error(w, "Unauthorized", 401) | ||
return | ||
} | ||
currentTimestampInt, err := strconv.Atoi(currentTimestamp) | ||
if err != nil { | ||
http.Error(w, "Internal Server Error", 500) | ||
return | ||
} | ||
requestTimestampInt, err := strconv.Atoi(requestTimestamp) | ||
if err != nil { | ||
http.Error(w, "Bad Request", 400) | ||
return | ||
} | ||
if int(math.Abs(float64(currentTimestampInt-requestTimestampInt))) > 10 { | ||
log.Println("Timestamp is older than 10 seconds") | ||
http.Error(w, "Bad Request", 400) | ||
return | ||
} else { | ||
log.Println("Timestamp is not older than 10 seconds") | ||
} | ||
} | ||
log.Println(string(body)) | ||
log.Println("--------------------------------------------------------------------------------------") | ||
}) | ||
|
||
http.ListenAndServe(":8081", nil) | ||
} |