vikunja/pkg/webhooks/runtime.go

144 lines
3.6 KiB
Go
Raw Normal View History

// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
2022-06-14 00:30:15 +00:00
package webhooks
import (
2022-06-20 06:46:09 +00:00
"bytes"
2022-06-14 00:30:15 +00:00
"fmt"
"net/http"
2022-06-20 06:46:09 +00:00
"strings"
2022-06-14 00:30:15 +00:00
"time"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"code.vikunja.io/api/pkg/config"
2022-06-20 06:46:09 +00:00
"code.vikunja.io/api/pkg/log"
2022-06-14 00:30:15 +00:00
"github.com/ThreeDotsLabs/watermill/message"
)
const (
defaultTimeout = 5 * time.Second
2022-06-20 06:46:09 +00:00
ctHeader = "Content-Type"
ctValue = "application/json"
hmacHeader = "X-Signature"
2022-06-14 00:30:15 +00:00
)
2022-06-20 06:46:09 +00:00
type FilteringFunction func(string) bool
type WebhookCallFunction func(string, *message.Message) error
2022-06-14 00:30:15 +00:00
// Single configuration entry
type SingleConfEntry struct {
2022-06-20 06:46:09 +00:00
Events []string `json:"events"`
URL string `json:"url"`
Secret string `json:"secret"`
Timeout int `json:"timeout"`
2022-06-14 00:30:15 +00:00
}
type WebhookRuntimeConfig struct {
2022-06-20 06:46:09 +00:00
FilterFunc FilteringFunction
ExecuteFunc WebhookCallFunction
2022-06-14 00:30:15 +00:00
}
func getWebhookFilterFunc(cfg SingleConfEntry) FilteringFunction {
return func(topic string) (is_interesting bool) {
2022-06-14 00:30:15 +00:00
for _, filter := range cfg.Events {
2022-06-20 06:46:09 +00:00
log.Debugf("Match pattern:'%s' topic:'%s'", filter, topic)
2022-06-14 00:30:15 +00:00
if filter == "*" {
log.Debugf(" '*' == Always match ")
return true
2022-06-20 06:46:09 +00:00
}
if strings.HasPrefix(topic, filter) {
log.Debugf("Positive match [%s] -> [%s]", filter, topic)
return true
2022-06-14 00:30:15 +00:00
}
}
log.Debugf("No match for [%s]", topic)
2022-06-14 00:30:15 +00:00
return false
}
}
func getWebhookCallFunc(cfg SingleConfEntry) WebhookCallFunction {
2022-06-20 06:46:09 +00:00
return func(topic string, msg *message.Message) error {
2022-06-14 00:30:15 +00:00
endpointURL := cfg.URL
hmacKey := cfg.Secret
timeout := defaultTimeout
if cfg.Timeout > 0 {
timeout = time.Second * time.Duration(cfg.Timeout)
2022-06-20 06:46:09 +00:00
}
2022-06-14 00:30:15 +00:00
log.Debugf("Webhook Call : %s (key=%s)", endpointURL, hmacKey)
2022-06-20 06:46:09 +00:00
webhookURL := fmt.Sprintf("%s%s", endpointURL, topic)
2022-06-14 00:30:15 +00:00
rawData := msg.Payload
2022-06-20 06:46:09 +00:00
req, err := http.NewRequest(http.MethodPost, webhookURL, bytes.NewBuffer(rawData))
if err != nil {
return err
2022-06-14 00:30:15 +00:00
}
client := &http.Client{
Timeout: timeout,
2022-06-14 00:30:15 +00:00
}
2022-06-20 06:46:09 +00:00
2022-06-14 00:30:15 +00:00
req.Header.Set(ctHeader, ctValue)
if len(hmacKey) > 1 {
signature := GenerateHMAC(rawData, hmacKey)
req.Header.Set(hmacHeader, signature)
}
2022-06-20 06:46:09 +00:00
resp, err = client.Do(req)
defer resp.Body.Close()
if err != nil {
2022-06-20 06:46:09 +00:00
log.Debugf("Webhook failed : %s , +%v", webhookURL, err)
return err
2022-06-14 00:30:15 +00:00
}
2022-06-20 06:46:09 +00:00
log.Debugf("Webhook success : %s ", webhookURL)
2022-06-14 00:30:15 +00:00
return nil
}
}
func GenerateHMAC(data []byte, key string) string {
h := hmac.New(sha256.New, []byte(key))
h.Write(data)
return hex.EncodeToString(h.Sum(nil))
}
// Process config and prepare mapping
2022-06-20 06:46:09 +00:00
func ProcessConfig() []WebhookRuntimeConfig {
2022-06-14 00:30:15 +00:00
var items []SingleConfEntry
config.WebhooksConf.GetUnmarshaled(&items)
2022-06-14 00:30:15 +00:00
runtime := make([]WebhookRuntimeConfig, len(items))
log.Debugf("Webhook config items : %+v\n", items)
2022-06-14 00:30:15 +00:00
for i, item := range items {
2022-06-20 06:46:09 +00:00
runtime[i].FilterFunc = getWebhookFilterFunc(item)
runtime[i].ExecuteFunc = getWebhookCallFunc(item)
2022-06-14 00:30:15 +00:00
}
return runtime
}