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.
363 lines
9.2 KiB
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
|
|
}
|
|
}
|