2024-02-11 08:56:32 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2024-02-12 04:07:14 +00:00
|
|
|
giteaTypes "code.gitea.io/gitea/modules/structs"
|
2024-02-11 08:56:32 +00:00
|
|
|
"encoding/json"
|
|
|
|
"git.lumeweb.com/LumeWeb/gitea-github-proxy/config"
|
|
|
|
"git.lumeweb.com/LumeWeb/gitea-github-proxy/db/model"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
2024-02-11 10:35:15 +00:00
|
|
|
"regexp"
|
|
|
|
"strings"
|
2024-02-11 08:56:32 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type WebhookManager struct {
|
|
|
|
config *config.Config
|
|
|
|
db *gorm.DB
|
|
|
|
logger *zap.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
type IncomingWebhookData struct {
|
|
|
|
Headers http.Header
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewWebhookManager(cfg *config.Config, db *gorm.DB, logger *zap.Logger) *WebhookManager {
|
|
|
|
return &WebhookManager{config: cfg, db: db, logger: logger}
|
|
|
|
}
|
|
|
|
|
2024-02-12 04:07:14 +00:00
|
|
|
func (whm *WebhookManager) HandlePullRequest(request *giteaTypes.PullRequestPayload, r *http.Request) {
|
2024-02-11 09:42:01 +00:00
|
|
|
ghEvent := convertPullRequestEvent(request)
|
2024-02-12 09:14:46 +00:00
|
|
|
githubAction := translatePrAction(request.Action, true)
|
2024-02-11 08:56:32 +00:00
|
|
|
r.Header.Set("X-GitHub-Event", githubAction)
|
|
|
|
|
|
|
|
whm.sendWebhooks(ghEvent, r)
|
|
|
|
}
|
|
|
|
func (whm *WebhookManager) sendWebhooks(request interface{}, r *http.Request) {
|
|
|
|
var apps []model.Apps
|
|
|
|
result := whm.db.Find(&apps)
|
|
|
|
if result.Error != nil {
|
|
|
|
whm.logger.Error("Failed to query apps", zap.Error(result.Error))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, app := range apps {
|
|
|
|
go func(app model.Apps) {
|
2024-02-12 04:07:14 +00:00
|
|
|
|
2024-02-12 04:23:32 +00:00
|
|
|
payloadBytes, err := json.Marshal(request)
|
|
|
|
if err != nil {
|
|
|
|
whm.logger.Error("Failed to marshal payload", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var rawMap map[string]interface{}
|
|
|
|
err = json.Unmarshal(payloadBytes, &rawMap)
|
|
|
|
if err != nil {
|
|
|
|
whm.logger.Error("Failed to unmarshal payload", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
2024-02-12 04:07:14 +00:00
|
|
|
|
|
|
|
rawMap["installation"] = struct {
|
|
|
|
ID uint `json:"id"`
|
|
|
|
}{ID: app.ID}
|
|
|
|
|
2024-02-12 04:25:43 +00:00
|
|
|
payloadBytes, err = json.Marshal(rawMap)
|
2024-02-11 08:56:32 +00:00
|
|
|
if err != nil {
|
|
|
|
whm.logger.Error("Failed to marshal payload", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("POST", app.WebhookUrl, bytes.NewBuffer(payloadBytes))
|
|
|
|
if err != nil {
|
|
|
|
whm.logger.Error("Failed to create request", zap.Error(err), zap.String("url", app.WebhookUrl))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
|
|
|
for key, values := range r.Header {
|
|
|
|
for _, value := range values {
|
|
|
|
req.Header.Add(key, value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-11 10:38:36 +00:00
|
|
|
payload := toNormalizedJson(payloadBytes)
|
2024-02-11 10:35:15 +00:00
|
|
|
|
2024-02-11 08:56:32 +00:00
|
|
|
signature := generatePayloadSignature(payload, app.WebhookSecret)
|
2024-02-11 11:20:18 +00:00
|
|
|
req.Header.Set("X-Hub-Signature-256", signature)
|
2024-02-11 08:56:32 +00:00
|
|
|
|
|
|
|
client := &http.Client{}
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
whm.logger.Error("Failed to send webhook", zap.Error(err), zap.String("url", app.WebhookUrl))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer func(Body io.ReadCloser) {
|
|
|
|
err := Body.Close()
|
|
|
|
if err != nil {
|
|
|
|
whm.logger.Error("Failed to close response body", zap.Error(err))
|
|
|
|
}
|
|
|
|
}(resp.Body)
|
|
|
|
|
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
|
|
|
whm.logger.Info("Webhook sent successfully", zap.String("url", app.WebhookUrl))
|
|
|
|
} else {
|
|
|
|
whm.logger.Error("Webhook failed", zap.String("url", app.WebhookUrl), zap.Int("status", resp.StatusCode))
|
|
|
|
}
|
|
|
|
}(app)
|
|
|
|
}
|
2024-02-11 10:35:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ported from https://github.com/octokit/webhooks.js/blob/984f3f51e077dbc7637aa55406c3bb114dbb7f82/src/to-normalized-json-string.ts
|
|
|
|
*/
|
|
|
|
|
|
|
|
func toNormalizedJson(payload []byte) []byte {
|
|
|
|
jsonString := string(payload)
|
|
|
|
|
|
|
|
// Regex to find Unicode escape sequences
|
|
|
|
re := regexp.MustCompile(`\\u([\da-fA-F]{4})`)
|
|
|
|
|
|
|
|
// Function to convert found sequences to uppercase
|
|
|
|
replaceFunc := func(match string) string {
|
|
|
|
parts := strings.Split(match, "\\u")
|
|
|
|
// Convert the Unicode sequence part to uppercase
|
|
|
|
return parts[0] + "\\u" + strings.ToUpper(parts[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replace the matches in the jsonString
|
|
|
|
normalizedString := re.ReplaceAllStringFunc(jsonString, replaceFunc)
|
2024-02-11 08:56:32 +00:00
|
|
|
|
2024-02-11 10:35:15 +00:00
|
|
|
return []byte(normalizedString)
|
2024-02-11 08:56:32 +00:00
|
|
|
}
|