You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-scm/scm/driver/gitea/webhook.go

363 lines
9.2 KiB
Go

// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gitea
import (
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"git.awesome-for.me/liuzhiguo/go-scm/scm"
"git.awesome-for.me/liuzhiguo/go-scm/scm/driver/internal/hmac"
)
type webhookService struct {
client *wrapper
}
func (s *webhookService) Parse(req *http.Request, fn scm.SecretFunc) (scm.Webhook, error) {
data, err := ioutil.ReadAll(
io.LimitReader(req.Body, 10000000),
)
if err != nil {
return nil, err
}
var hook scm.Webhook
switch req.Header.Get("X-Gitea-Event") {
case "push":
hook, err = s.parsePushHook(data)
case "create":
hook, err = s.parseCreateHook(data)
case "delete":
hook, err = s.parseDeleteHook(data)
case "issues":
hook, err = s.parseIssueHook(data)
case "issue_comment":
hook, err = s.parseIssueCommentHook(data)
case "pull_request":
hook, err = s.parsePullRequestHook(data)
default:
return nil, scm.ErrUnknownEvent
}
if err != nil {
return nil, err
}
// get the gitea signature key to verify the payload
// signature. If no key is provided, no validation
// is performed.
key, err := fn(hook)
if err != nil {
return hook, err
} else if key == "" {
return hook, nil
}
secret := req.FormValue("secret")
signature := req.Header.Get("X-Gitea-Signature")
// fail if no signature passed
if signature == "" && secret == "" {
return hook, scm.ErrSignatureInvalid
}
// test signature if header not set and secret is in payload
if signature == "" && secret != "" && secret != key {
return hook, scm.ErrSignatureInvalid
}
// test signature using header
if signature != "" && !hmac.Validate(sha256.New, data, []byte(key), signature) {
return hook, scm.ErrSignatureInvalid
}
return hook, nil
}
func (s *webhookService) parsePushHook(data []byte) (scm.Webhook, error) {
dst := new(pushHook)
err := json.Unmarshal(data, dst)
return convertPushHook(dst), err
}
func (s *webhookService) parseCreateHook(data []byte) (scm.Webhook, error) {
dst := new(createHook)
err := json.Unmarshal(data, dst)
switch dst.RefType {
case "tag":
return convertTagHook(dst, scm.ActionCreate), err
case "branch":
return convertBranchHook(dst, scm.ActionCreate), err
default:
return nil, scm.ErrUnknownEvent
}
}
func (s *webhookService) parseDeleteHook(data []byte) (scm.Webhook, error) {
dst := new(createHook)
err := json.Unmarshal(data, dst)
switch dst.RefType {
case "tag":
return convertTagHook(dst, scm.ActionDelete), err
case "branch":
return convertBranchHook(dst, scm.ActionDelete), err
default:
return nil, scm.ErrUnknownEvent
}
}
func (s *webhookService) parseIssueHook(data []byte) (scm.Webhook, error) {
dst := new(issueHook)
err := json.Unmarshal(data, dst)
return convertIssueHook(dst), err
}
func (s *webhookService) parseIssueCommentHook(data []byte) (scm.Webhook, error) {
dst := new(issueHook)
err := json.Unmarshal(data, dst)
if dst.Issue.PullRequest != nil {
return convertPullRequestCommentHook(dst), err
}
return convertIssueCommentHook(dst), err
}
func (s *webhookService) parsePullRequestHook(data []byte) (scm.Webhook, error) {
dst := new(pullRequestHook)
err := json.Unmarshal(data, dst)
return convertPullRequestHook(dst), err
}
//
// native data structures
//
type (
// gitea push webhook payload
pushHook struct {
Ref string `json:"ref"`
Before string `json:"before"`
After string `json:"after"`
Compare string `json:"compare_url"`
Commits []commit `json:"commits"`
Repository repository `json:"repository"`
Pusher user `json:"pusher"`
Sender user `json:"sender"`
}
// gitea create webhook payload
createHook struct {
Ref string `json:"ref"`
RefType string `json:"ref_type"`
Sha string `json:"sha"`
DefaultBranch string `json:"default_branch"`
Repository repository `json:"repository"`
Sender user `json:"sender"`
}
// gitea issue webhook payload
issueHook struct {
Action string `json:"action"`
Issue issue `json:"issue"`
Comment issueComment `json:"comment"`
Repository repository `json:"repository"`
Sender user `json:"sender"`
}
// gitea pull request webhook payload
pullRequestHook struct {
Action string `json:"action"`
Number int `json:"number"`
PullRequest pr `json:"pull_request"`
Repository repository `json:"repository"`
Sender user `json:"sender"`
}
)
//
// native data structure conversion
//
func convertTagHook(dst *createHook, action scm.Action) *scm.TagHook {
return &scm.TagHook{
Action: action,
Ref: scm.Reference{
Name: dst.Ref,
Sha: dst.Sha,
},
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertBranchHook(dst *createHook, action scm.Action) *scm.BranchHook {
return &scm.BranchHook{
Action: action,
Ref: scm.Reference{
Name: dst.Ref,
},
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertPushHook(dst *pushHook) *scm.PushHook {
if len(dst.Commits) > 0 {
var commits []scm.Commit
for _, c := range dst.Commits {
commits = append(commits,
scm.Commit{
Sha: c.ID,
Message: c.Message,
Link: c.URL,
Author: scm.Signature{
Login: c.Author.Username,
Email: c.Author.Email,
Name: c.Author.Name,
Date: c.Timestamp,
},
Committer: scm.Signature{
Login: c.Committer.Username,
Email: c.Committer.Email,
Name: c.Committer.Name,
Date: c.Timestamp,
},
})
}
return &scm.PushHook{
Ref: dst.Ref,
Before: dst.Before,
Commit: scm.Commit{
Sha: dst.After,
Message: dst.Commits[0].Message,
Link: dst.Compare,
Author: scm.Signature{
Login: dst.Commits[0].Author.Username,
Email: dst.Commits[0].Author.Email,
Name: dst.Commits[0].Author.Name,
Date: dst.Commits[0].Timestamp,
},
Committer: scm.Signature{
Login: dst.Commits[0].Committer.Username,
Email: dst.Commits[0].Committer.Email,
Name: dst.Commits[0].Committer.Name,
Date: dst.Commits[0].Timestamp,
},
},
Commits: commits,
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
} else {
return &scm.PushHook{
Ref: dst.Ref,
Commit: scm.Commit{
Sha: dst.After,
Link: dst.Compare,
Author: scm.Signature{
Login: dst.Pusher.Login,
Email: dst.Pusher.Email,
Name: dst.Pusher.Fullname,
},
Committer: scm.Signature{
Login: dst.Pusher.Login,
Email: dst.Pusher.Email,
Name: dst.Pusher.Fullname,
},
},
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
}
func convertPullRequestHook(dst *pullRequestHook) *scm.PullRequestHook {
return &scm.PullRequestHook{
Action: convertAction(dst.Action),
PullRequest: scm.PullRequest{
Number: dst.PullRequest.Number,
Title: dst.PullRequest.Title,
Body: dst.PullRequest.Body,
Closed: dst.PullRequest.State == "closed",
Author: scm.User{
Login: dst.PullRequest.User.Login,
Email: dst.PullRequest.User.Email,
Avatar: dst.PullRequest.User.Avatar,
},
Merged: dst.PullRequest.Merged,
// Created: nil,
// Updated: nil,
Source: dst.PullRequest.Head.Name,
Target: dst.PullRequest.Base.Name,
Fork: dst.PullRequest.Head.Repo.FullName,
Link: dst.PullRequest.HTMLURL,
Ref: fmt.Sprintf("refs/pull/%d/head", dst.PullRequest.Number),
Sha: dst.PullRequest.Head.Sha,
},
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertPullRequestCommentHook(dst *issueHook) *scm.PullRequestCommentHook {
return &scm.PullRequestCommentHook{
Action: convertAction(dst.Action),
PullRequest: *convertPullRequestFromIssue(&dst.Issue),
Comment: *convertIssueComment(&dst.Comment),
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertIssueHook(dst *issueHook) *scm.IssueHook {
return &scm.IssueHook{
Action: convertAction(dst.Action),
Issue: *convertIssue(&dst.Issue),
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertIssueCommentHook(dst *issueHook) *scm.IssueCommentHook {
return &scm.IssueCommentHook{
Action: convertAction(dst.Action),
Issue: *convertIssue(&dst.Issue),
Comment: *convertIssueComment(&dst.Comment),
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertAction(src string) (action scm.Action) {
switch src {
case "create", "created":
return scm.ActionCreate
case "delete", "deleted":
return scm.ActionDelete
case "update", "updated", "edit", "edited":
return scm.ActionUpdate
case "open", "opened":
return scm.ActionOpen
case "reopen", "reopened":
return scm.ActionReopen
case "close", "closed":
return scm.ActionClose
case "label", "labeled":
return scm.ActionLabel
case "unlabel", "unlabeled":
return scm.ActionUnlabel
case "merge", "merged":
return scm.ActionMerge
case "synchronize", "synchronized":
return scm.ActionSync
default:
return
}
}