mirror of
https://github.com/semaphoreui/semaphore.git
synced 2024-11-23 20:35:24 +01:00
301 lines
7.6 KiB
Go
301 lines
7.6 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha1"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
"github.com/ansible-semaphore/semaphore/api/helpers"
|
|
"github.com/ansible-semaphore/semaphore/db"
|
|
jsonq "github.com/thedevsaddam/gojsonq/v2"
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
|
|
func ReceiveIntegration(w http.ResponseWriter, r *http.Request) {
|
|
log.Info(fmt.Sprintf("Receiving Integration from: %s", r.RemoteAddr))
|
|
|
|
var err error
|
|
// var projects []db.Project
|
|
var extractors []db.IntegrationExtractor
|
|
extractors, err = helpers.Store(r).GetAllIntegrationExtractors()
|
|
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
|
|
var foundExtractors = make([]db.IntegrationExtractor, 0)
|
|
for _, extractor := range extractors {
|
|
var matchers []db.IntegrationMatcher
|
|
matchers, err = helpers.Store(r).GetIntegrationMatchers(extractor.ID, db.RetrieveQueryParams{})
|
|
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 {
|
|
var integrationIDs = make([]int, 0)
|
|
var extractorIDs = make([]int, 0)
|
|
|
|
for _, extractor := range foundExtractors {
|
|
integrationIDs = append(integrationIDs, extractor.IntegrationID)
|
|
}
|
|
|
|
for _, extractor := range foundExtractors {
|
|
extractorIDs = append(extractorIDs, extractor.ID)
|
|
}
|
|
|
|
var allIntegrationExtractorIDs = make([]int, 0)
|
|
var integrations []db.Integration
|
|
integrations, err = helpers.Store(r).GetAllIntegrations()
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
for _, id := range integrationIDs {
|
|
var extractorsForIntegration []db.IntegrationExtractor
|
|
extractorsForIntegration, err = helpers.Store(r).GetIntegrationExtractors(id, db.RetrieveQueryParams{})
|
|
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
|
|
for _, extractor := range extractorsForIntegration {
|
|
allIntegrationExtractorIDs = append(allIntegrationExtractorIDs, extractor.ID)
|
|
}
|
|
|
|
var found = false
|
|
for _, integrationExtractorID := range extractorIDs {
|
|
if slices.Contains(allIntegrationExtractorIDs, integrationExtractorID) {
|
|
found = true
|
|
continue
|
|
} else {
|
|
found = false
|
|
break
|
|
}
|
|
}
|
|
|
|
// if all extractors for a integration matched during search
|
|
if found {
|
|
integration := FindIntegration(integrations, id)
|
|
|
|
if integration.ID != id {
|
|
log.Error(fmt.Sprintf("Could not find integration ID: %v", id))
|
|
continue
|
|
}
|
|
RunIntegration(integration, r)
|
|
}
|
|
}
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func FindIntegration(integrations []db.Integration, id int) (integration db.Integration) {
|
|
for _, integration := range integrations {
|
|
if integration.ID == id {
|
|
return integration
|
|
}
|
|
}
|
|
return db.Integration{}
|
|
}
|
|
|
|
func UniqueIntegrations(integrations []db.Integration) []db.Integration {
|
|
var unique []db.Integration
|
|
integrationLoop:
|
|
for _, v := range integrations {
|
|
for i, u := range unique {
|
|
if v.ID == u.ID {
|
|
unique[i] = v
|
|
continue integrationLoop
|
|
}
|
|
}
|
|
unique = append(unique, v)
|
|
}
|
|
return unique
|
|
}
|
|
|
|
func UniqueExtractors(extractors []db.IntegrationExtractor) []db.IntegrationExtractor {
|
|
var unique []db.IntegrationExtractor
|
|
integrationLoop:
|
|
for _, v := range extractors {
|
|
for i, u := range unique {
|
|
if v.ID == u.ID {
|
|
unique[i] = v
|
|
continue integrationLoop
|
|
}
|
|
}
|
|
unique = append(unique, v)
|
|
}
|
|
return unique
|
|
}
|
|
|
|
func Match(matcher db.IntegrationMatcher, r *http.Request) (matched bool) {
|
|
|
|
switch matcher.MatchType {
|
|
case db.IntegrationMatchHeader:
|
|
var header_value = r.Header.Get(matcher.Key)
|
|
return MatchCompare(header_value, matcher.Method, matcher.Value)
|
|
case db.IntegrationMatchBody:
|
|
bodyBytes, err := io.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
return false
|
|
}
|
|
var body = string(bodyBytes)
|
|
switch matcher.BodyDataType {
|
|
case db.IntegrationBodyDataJSON:
|
|
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)
|
|
case db.IntegrationBodyDataString:
|
|
return MatchCompare(body, matcher.Method, matcher.Value)
|
|
case db.IntegrationBodyDataXML:
|
|
// XXX: TBI
|
|
return false
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func MatchCompare(value string, method db.IntegrationMatchMethodType, expected string) bool {
|
|
switch method {
|
|
case db.IntegrationMatchMethodEquals:
|
|
return value == expected
|
|
case db.IntegrationMatchMethodUnEquals:
|
|
return value != expected
|
|
case db.IntegrationMatchMethodContains:
|
|
return strings.Contains(value, expected)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func RunIntegration(integration db.Integration, r *http.Request) {
|
|
extractors, err := helpers.Store(r).GetIntegrationExtractors(integration.ID, db.RetrieveQueryParams{})
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
|
|
var extractValues = make([]db.IntegrationExtractValue, 0)
|
|
for _, extractor := range extractors {
|
|
extractValuesForExtractor, errextractValuesForExtractor := helpers.Store(r).GetIntegrationExtractValues(extractor.ID, db.RetrieveQueryParams{})
|
|
if errextractValuesForExtractor != nil {
|
|
log.Error(errextractValuesForExtractor)
|
|
return
|
|
}
|
|
extractValues = append(extractValues, extractValuesForExtractor...)
|
|
}
|
|
|
|
var extractedResults = Extract(extractValues, r)
|
|
|
|
// XXX: LOG AN EVENT HERE
|
|
environmentJSONBytes, err := json.Marshal(extractedResults)
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
|
|
var environmentJSONString = string(environmentJSONBytes)
|
|
var taskDefinition = db.Task{
|
|
TemplateID: integration.TemplateID,
|
|
ProjectID: integration.ProjectID,
|
|
Debug: true,
|
|
Environment: environmentJSONString,
|
|
}
|
|
|
|
var user db.User
|
|
user, err = helpers.Store(r).GetUser(1)
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
|
|
helpers.TaskPool(r).AddTask(taskDefinition, &user.ID, integration.ProjectID)
|
|
}
|
|
|
|
func Extract(extractValues []db.IntegrationExtractValue, r *http.Request) (result map[string]string) {
|
|
result = make(map[string]string)
|
|
|
|
for _, extractValue := range extractValues {
|
|
switch extractValue.ValueSource {
|
|
case db.IntegrationExtractHeaderValue:
|
|
result[extractValue.Variable] = r.Header.Get(extractValue.Key)
|
|
case db.IntegrationExtractBodyValue:
|
|
bodyBytes, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
return
|
|
}
|
|
var body = string(bodyBytes)
|
|
|
|
switch extractValue.BodyDataType {
|
|
case db.IntegrationBodyDataJSON:
|
|
var jsonBytes bytes.Buffer
|
|
jsonq.New().FromString(body).From(extractValue.Key).Writer(&jsonBytes)
|
|
result[extractValue.Variable] = jsonBytes.String()
|
|
case db.IntegrationBodyDataString:
|
|
result[extractValue.Variable] = body
|
|
case db.IntegrationBodyDataXML:
|
|
// XXX: TBI
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|