Semaphore/api/integration.go

333 lines
8.5 KiB
Go
Raw Normal View History

2023-07-03 01:41:13 +02:00
package api
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
2024-01-15 20:35:47 +01:00
"encoding/json"
"fmt"
2024-02-12 11:20:50 +01:00
"github.com/gorilla/context"
2024-01-15 20:35:47 +01:00
"io"
"net/http"
2023-07-03 01:41:13 +02:00
"strings"
2024-01-15 20:35:47 +01:00
2023-07-03 01:41:13 +02:00
"github.com/ansible-semaphore/semaphore/api/helpers"
"github.com/ansible-semaphore/semaphore/db"
2024-03-03 11:57:39 +01:00
log "github.com/sirupsen/logrus"
2023-07-03 01:41:13 +02:00
jsonq "github.com/thedevsaddam/gojsonq/v2"
2024-01-15 20:35:47 +01:00
"golang.org/x/exp/slices"
2023-07-03 01:41:13 +02:00
)
// IsValidPayload checks if the github payload's hash fits with
// the hash computed by GitHub sent as a header
func IsValidPayload(secret, headerHash string, payload []byte) bool {
hash := HashPayload(secret, payload)
return hmac.Equal(
[]byte(hash),
[]byte(headerHash),
)
}
// HashPayload computes the hash of payload's body according to the webhook's secret token
// see https://developer.github.com/webhooks/securing/#validating-payloads-from-github
// returning the hash as a hexadecimal string
func HashPayload(secret string, playloadBody []byte) string {
hm := hmac.New(sha1.New, []byte(secret))
hm.Write(playloadBody)
sum := hm.Sum(nil)
return fmt.Sprintf("%x", sum)
}
2024-02-11 20:52:14 +01:00
func ReceiveIntegration(w http.ResponseWriter, r *http.Request) {
log.Info(fmt.Sprintf("Receiving Integration from: %s", r.RemoteAddr))
2023-07-03 01:41:13 +02:00
var err error
// var projects []db.Project
2024-02-11 20:52:14 +01:00
var extractors []db.IntegrationExtractor
2024-02-12 11:20:50 +01:00
if context.Get(r, "integration") != nil {
integration := context.Get(r, "integration").(db.Integration)
switch integration.AuthMethod {
case db.IntegrationAuthHmac:
var payload []byte
_, err = r.Body.Read(payload)
if err != nil {
log.Error(err)
return
}
if IsValidPayload(integration.AuthSecret.LoginPassword.Password, r.Header.Get(integration.AuthHeader), payload) {
log.Error(err)
return
}
case db.IntegrationAuthToken:
if integration.AuthSecret.LoginPassword.Password != r.Header.Get(integration.AuthHeader) {
log.Error("Invalid verification token")
return
}
case db.IntegrationAuthNone:
default:
log.Error("Unknown verification method: " + integration.AuthMethod)
return
}
extractors, err = helpers.Store(r).GetIntegrationExtractors(integration.ID, db.RetrieveQueryParams{})
} else {
// TODO: remove
extractors, err = helpers.Store(r).GetAllIntegrationExtractors()
}
2023-07-03 01:41:13 +02:00
2024-01-15 20:35:47 +01:00
if err != nil {
log.Error(err)
return
}
2024-02-11 20:52:14 +01:00
var foundExtractors = make([]db.IntegrationExtractor, 0)
2023-07-03 01:41:13 +02:00
for _, extractor := range extractors {
2024-02-11 20:52:14 +01:00
var matchers []db.IntegrationMatcher
matchers, err = helpers.Store(r).GetIntegrationMatchers(extractor.ID, db.RetrieveQueryParams{})
2023-07-03 01:41:13 +02:00
if err != nil {
log.Error(err)
}
var matched = false
for _, matcher := range matchers {
if Match(matcher, r) {
matched = true
continue
} else {
matched = false
break
}
}
// If all Matched...
if matched {
foundExtractors = append(foundExtractors, extractor)
}
}
// Iterate over all Extractors that matched
if len(foundExtractors) > 0 {
2024-02-11 20:52:14 +01:00
var integrationIDs = make([]int, 0)
2023-07-03 01:41:13 +02:00
var extractorIDs = make([]int, 0)
for _, extractor := range foundExtractors {
2024-02-11 20:52:14 +01:00
integrationIDs = append(integrationIDs, extractor.IntegrationID)
2023-07-03 01:41:13 +02:00
}
for _, extractor := range foundExtractors {
extractorIDs = append(extractorIDs, extractor.ID)
}
2024-02-11 20:52:14 +01:00
var allIntegrationExtractorIDs = make([]int, 0)
var integrations []db.Integration
integrations, err = helpers.Store(r).GetAllIntegrations()
2024-01-15 20:35:47 +01:00
if err != nil {
log.Error(err)
return
}
2024-02-11 20:52:14 +01:00
for _, id := range integrationIDs {
var extractorsForIntegration []db.IntegrationExtractor
extractorsForIntegration, err = helpers.Store(r).GetIntegrationExtractors(id, db.RetrieveQueryParams{})
2023-07-03 01:41:13 +02:00
2024-01-15 20:35:47 +01:00
if err != nil {
log.Error(err)
return
}
2024-02-11 20:52:14 +01:00
for _, extractor := range extractorsForIntegration {
allIntegrationExtractorIDs = append(allIntegrationExtractorIDs, extractor.ID)
2023-07-03 01:41:13 +02:00
}
var found = false
2024-02-11 20:52:14 +01:00
for _, integrationExtractorID := range extractorIDs {
if slices.Contains(allIntegrationExtractorIDs, integrationExtractorID) {
2023-07-03 01:41:13 +02:00
found = true
continue
} else {
found = false
break
}
}
2024-02-11 20:52:14 +01:00
// if all extractors for a integration matched during search
2023-07-03 01:41:13 +02:00
if found {
2024-02-11 20:52:14 +01:00
integration := FindIntegration(integrations, id)
2024-01-15 20:35:47 +01:00
2024-02-11 20:52:14 +01:00
if integration.ID != id {
log.Error(fmt.Sprintf("Could not find integration ID: %v", id))
2023-07-03 01:41:13 +02:00
continue
}
2024-02-11 20:52:14 +01:00
RunIntegration(integration, r)
2023-07-03 01:41:13 +02:00
}
}
}
w.WriteHeader(http.StatusNoContent)
}
2024-02-11 20:52:14 +01:00
func FindIntegration(integrations []db.Integration, id int) (integration db.Integration) {
for _, integration := range integrations {
if integration.ID == id {
return integration
2023-07-03 01:41:13 +02:00
}
}
2024-02-11 20:52:14 +01:00
return db.Integration{}
2023-07-03 01:41:13 +02:00
}
2024-02-11 20:52:14 +01:00
func UniqueIntegrations(integrations []db.Integration) []db.Integration {
var unique []db.Integration
integrationLoop:
for _, v := range integrations {
2024-01-15 20:35:47 +01:00
for i, u := range unique {
if v.ID == u.ID {
unique[i] = v
2024-02-11 20:52:14 +01:00
continue integrationLoop
2024-01-15 20:35:47 +01:00
}
}
unique = append(unique, v)
}
return unique
2023-07-03 01:41:13 +02:00
}
2024-02-11 20:52:14 +01:00
func UniqueExtractors(extractors []db.IntegrationExtractor) []db.IntegrationExtractor {
var unique []db.IntegrationExtractor
integrationLoop:
2024-01-15 20:35:47 +01:00
for _, v := range extractors {
for i, u := range unique {
if v.ID == u.ID {
unique[i] = v
2024-02-11 20:52:14 +01:00
continue integrationLoop
2024-01-15 20:35:47 +01:00
}
}
unique = append(unique, v)
}
return unique
2023-07-03 01:41:13 +02:00
}
2024-02-11 20:52:14 +01:00
func Match(matcher db.IntegrationMatcher, r *http.Request) (matched bool) {
2024-01-15 20:35:47 +01:00
switch matcher.MatchType {
2024-02-11 20:52:14 +01:00
case db.IntegrationMatchHeader:
2024-01-15 20:35:47 +01:00
var header_value = r.Header.Get(matcher.Key)
2023-07-03 01:41:13 +02:00
return MatchCompare(header_value, matcher.Method, matcher.Value)
2024-02-11 20:52:14 +01:00
case db.IntegrationMatchBody:
2023-07-03 01:41:13 +02:00
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
log.Fatalln(err)
return false
}
var body = string(bodyBytes)
2024-01-15 20:35:47 +01:00
switch matcher.BodyDataType {
2024-02-11 20:52:14 +01:00
case db.IntegrationBodyDataJSON:
2023-07-03 01:41:13 +02:00
var jsonBytes bytes.Buffer
jsonq.New().FromString(body).From(matcher.Key).Writer(&jsonBytes)
var jsonString = jsonBytes.String()
if err != nil {
log.Error(fmt.Sprintf("Failed to marshal JSON contents of body. %v", err))
}
return MatchCompare(jsonString, matcher.Method, matcher.Value)
2024-02-11 20:52:14 +01:00
case db.IntegrationBodyDataString:
2023-07-03 01:41:13 +02:00
return MatchCompare(body, matcher.Method, matcher.Value)
2024-02-11 20:52:14 +01:00
case db.IntegrationBodyDataXML:
2023-07-03 01:41:13 +02:00
// XXX: TBI
return false
}
}
2024-01-15 20:35:47 +01:00
2023-07-03 01:41:13 +02:00
return false
}
2024-02-11 20:52:14 +01:00
func MatchCompare(value string, method db.IntegrationMatchMethodType, expected string) bool {
2024-01-15 20:35:47 +01:00
switch method {
2024-02-11 20:52:14 +01:00
case db.IntegrationMatchMethodEquals:
2023-07-03 01:41:13 +02:00
return value == expected
2024-02-11 20:52:14 +01:00
case db.IntegrationMatchMethodUnEquals:
2023-07-03 01:41:13 +02:00
return value != expected
2024-02-11 20:52:14 +01:00
case db.IntegrationMatchMethodContains:
2023-07-03 01:41:13 +02:00
return strings.Contains(value, expected)
2024-01-15 20:35:47 +01:00
default:
return false
2023-07-03 01:41:13 +02:00
}
}
2024-02-11 20:52:14 +01:00
func RunIntegration(integration db.Integration, r *http.Request) {
extractors, err := helpers.Store(r).GetIntegrationExtractors(integration.ID, db.RetrieveQueryParams{})
2023-07-03 01:41:13 +02:00
if err != nil {
log.Error(err)
return
}
if err != nil {
log.Error(err)
return
}
2024-02-11 20:52:14 +01:00
var extractValues = make([]db.IntegrationExtractValue, 0)
2023-07-03 01:41:13 +02:00
for _, extractor := range extractors {
2024-02-11 20:52:14 +01:00
extractValuesForExtractor, errextractValuesForExtractor := helpers.Store(r).GetIntegrationExtractValues(extractor.ID, db.RetrieveQueryParams{})
2024-01-15 20:35:47 +01:00
if errextractValuesForExtractor != nil {
log.Error(errextractValuesForExtractor)
return
2023-07-03 01:41:13 +02:00
}
2024-01-15 20:35:47 +01:00
extractValues = append(extractValues, extractValuesForExtractor...)
2023-07-03 01:41:13 +02:00
}
var extractedResults = Extract(extractValues, r)
// XXX: LOG AN EVENT HERE
environmentJSONBytes, err := json.Marshal(extractedResults)
2024-01-15 20:35:47 +01:00
if err != nil {
log.Error(err)
return
}
2023-07-03 01:41:13 +02:00
var environmentJSONString = string(environmentJSONBytes)
var taskDefinition = db.Task{
2024-02-11 20:52:14 +01:00
TemplateID: integration.TemplateID,
ProjectID: integration.ProjectID,
2024-01-15 20:35:47 +01:00
Debug: true,
2023-07-03 01:41:13 +02:00
Environment: environmentJSONString,
}
var user db.User
user, err = helpers.Store(r).GetUser(1)
2024-01-15 20:35:47 +01:00
if err != nil {
log.Error(err)
return
}
2023-07-03 01:41:13 +02:00
2024-02-11 20:52:14 +01:00
helpers.TaskPool(r).AddTask(taskDefinition, &user.ID, integration.ProjectID)
2023-07-03 01:41:13 +02:00
}
2024-02-11 20:52:14 +01:00
func Extract(extractValues []db.IntegrationExtractValue, r *http.Request) (result map[string]string) {
2023-07-03 01:41:13 +02:00
result = make(map[string]string)
for _, extractValue := range extractValues {
2024-01-15 20:35:47 +01:00
switch extractValue.ValueSource {
2024-02-11 20:52:14 +01:00
case db.IntegrationExtractHeaderValue:
2023-07-03 01:41:13 +02:00
result[extractValue.Variable] = r.Header.Get(extractValue.Key)
2024-02-11 20:52:14 +01:00
case db.IntegrationExtractBodyValue:
2023-07-03 01:41:13 +02:00
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
2024-01-15 20:35:47 +01:00
log.Fatal(err)
2023-07-03 01:41:13 +02:00
return
}
var body = string(bodyBytes)
2024-01-15 20:35:47 +01:00
switch extractValue.BodyDataType {
2024-02-11 20:52:14 +01:00
case db.IntegrationBodyDataJSON:
2023-07-03 01:41:13 +02:00
var jsonBytes bytes.Buffer
jsonq.New().FromString(body).From(extractValue.Key).Writer(&jsonBytes)
result[extractValue.Variable] = jsonBytes.String()
2024-02-11 20:52:14 +01:00
case db.IntegrationBodyDataString:
2023-07-03 01:41:13 +02:00
result[extractValue.Variable] = body
2024-02-11 20:52:14 +01:00
case db.IntegrationBodyDataXML:
2023-07-03 01:41:13 +02:00
// XXX: TBI
}
}
}
return
}