/
github.go
133 lines (117 loc) · 3.44 KB
/
github.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package main
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"hash"
"io/ioutil"
"log"
"net/http"
"strings"
"golang.org/x/build/cmd/pubsubhelper/pubsubtypes"
)
func handleGithubWebhook(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil {
http.Error(w, "HTTPS required", http.StatusBadRequest)
return
}
body, err := validateGithubRequest(w, r)
if err != nil {
log.Printf("failed to validate github webhook request: %v", err)
// But send a 200 OK anyway, so they don't queue up on
// Github's side if they're real.
return
}
var payload githubWebhookPayload
if err := json.Unmarshal(body, &payload); err != nil {
log.Printf("error unmarshalling payload: %v; payload=%s", err, body)
// But send a 200 OK anyway. Our fault.
return
}
back, _ := json.MarshalIndent(payload, "", "\t")
log.Printf("github verified webhook: %s", back)
if payload.Repository == nil || (payload.Issue == nil && payload.PullRequest == nil) {
// Ignore.
return
}
f := strings.Split(payload.Repository.FullName, "/")
if len(f) != 2 {
log.Printf("bogus repository name %q", payload.Repository.FullName)
return
}
owner, repo := f[0], f[1]
var issueNumber int
if payload.Issue != nil {
issueNumber = payload.Issue.Number
}
var prNumber int
if payload.PullRequest != nil {
prNumber = payload.PullRequest.Number
}
publish(&pubsubtypes.Event{
GitHub: &pubsubtypes.GitHubEvent{
Action: payload.Action,
RepoOwner: owner,
Repo: repo,
IssueNumber: issueNumber,
PullRequestNumber: prNumber,
},
})
}
// validate compares the signature in the request header with the body.
func validateGithubRequest(w http.ResponseWriter, r *http.Request) (body []byte, err error) {
// Decode signature header.
sigHeader := r.Header.Get("X-Hub-Signature")
sigParts := strings.SplitN(sigHeader, "=", 2)
if len(sigParts) != 2 {
return nil, fmt.Errorf("Bad signature header: %q", sigHeader)
}
var h func() hash.Hash
switch alg := sigParts[0]; alg {
case "sha1":
h = sha1.New
case "sha256":
h = sha256.New
default:
return nil, fmt.Errorf("Unsupported hash algorithm: %q", alg)
}
gotSig, err := hex.DecodeString(sigParts[1])
if err != nil {
return nil, err
}
body, err = ioutil.ReadAll(http.MaxBytesReader(w, r.Body, 5<<20))
if err != nil {
return nil, err
}
// TODO(golang/go#37171): find a cleaner solution than using a global
mac := hmac.New(h, []byte(*webhookSecret))
mac.Write(body)
expectSig := mac.Sum(nil)
if !hmac.Equal(gotSig, expectSig) {
return nil, fmt.Errorf("Invalid signature %X, want %x", gotSig, expectSig)
}
return body, nil
}
type githubWebhookPayload struct {
Action string `json:"action"`
Repository *githubRepository `json:"repository"`
Issue *githubIssue `json:"issue"`
PullRequest *githubPullRequest `json:"pull_request"`
}
type githubRepository struct {
FullName string `json:"full_name"` // "golang/go"
}
type githubIssue struct {
URL string `json:"url"` // https://api.github.com/repos/baxterthehacker/public-repo/issues/2
Number int `json:"number"` // 2
}
type githubPullRequest struct {
URL string `json:"url"` // https://api.github.com/repos/baxterthehacker/public-repo/pulls/8
Number int `json:"number"` // 8
}