forked from ckingdev/gohook
-
Notifications
You must be signed in to change notification settings - Fork 1
/
server.go
163 lines (150 loc) · 4.28 KB
/
server.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package gohook
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
// Server is the basic type that listens for webhook events and pushes them onto
// the EventAndTypes channel. You should create a Server through the NewServer
// function.
type Server struct {
Port int
Secret string
Path string
EventAndTypes chan *EventAndType
}
// NewServer creates a new server with the given settings. If secret is the
// zero-length string, no security verification will be done. Path should be
// specified with a leading slash.
func NewServer(port int, secret string, path string) *Server {
return &Server{
Port: port,
Secret: secret,
Path: path,
EventAndTypes: make(chan *EventAndType, 5),
}
}
func (s *Server) verifyAuth(body []byte, req *http.Request) bool {
signature := req.Header.Get("X-Hub-Signature")
if signature == "" {
return false
}
mac := hmac.New(sha1.New, []byte(s.Secret))
mac.Write(body)
expectedMAC := mac.Sum(nil)
expectedSig := "sha1=" + hex.EncodeToString(expectedMAC)
if !hmac.Equal([]byte(expectedSig), []byte(signature)) {
return false
}
return true
}
func (s *Server) processPacket(eventType EventType, respBody []byte) {
var payload interface{}
switch eventType {
case CommitCommentEventType:
payload = &CommitCommentEvent{}
case CreateEventType:
payload = &CreateEvent{}
case DeleteEventType:
payload = &DeleteEvent{}
case DeploymentEventType:
payload = &DeploymentEvent{}
case DeploymentStatusEventType:
payload = &DeploymentStatusEvent{}
case ForkEventType:
payload = &ForkEvent{}
case GollumEventType:
payload = &GollumEvent{}
case IssueCommentEventType:
payload = &IssueCommentEvent{}
case IssuesEventType:
payload = &IssuesEvent{}
case MemberEventType:
payload = &MemberEvent{}
case MembershipEventType:
payload = &MembershipEvent{}
case PageBuildEventType:
payload = &PageBuildEvent{}
case PingEventType:
payload = &PingEvent{}
case PublicEventType:
payload = &PublicEvent{}
case PullRequestEventType:
payload = &PullRequestEvent{}
case PullRequestReviewCommentEventType:
payload = &PullRequestReviewCommentEvent{}
case PushEventType:
payload = &PushEvent{}
case ReleaseEventType:
payload = &ReleaseEvent{}
case RepositoryEventType:
payload = &RepositoryEvent{}
case StatusEventType:
payload = &StatusEvent{}
case TeamAddEventType:
payload = &TeamAddEvent{}
case WatchEventType:
payload = &WatchEvent{}
default:
log.Printf("Attempt to process unknown packet type: %s", eventType)
return
}
if err := json.Unmarshal(respBody, &payload); err != nil {
panic(err)
}
et := &EventAndType{
Event: payload,
Type: eventType,
}
s.EventAndTypes <- et
}
// ServeHTTP implements http.Handler interface on Server. You should never need
// to call this yourself.
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
if req.Method != "POST" {
http.Error(w, "405 Method not allowed", http.StatusMethodNotAllowed)
return
}
if req.URL.Path != s.Path {
http.Error(w, "404 Not found- expected "+s.Path, http.StatusNotFound)
return
}
eventType := req.Header.Get("X-Github-Event")
if eventType == "" {
http.Error(w, "400 Bad Request - Missing X-Github-Event Header", http.StatusBadRequest)
return
}
respBody, err := ioutil.ReadAll(req.Body)
if err != nil {
http.Error(w, "500 Internal Error - Could not read from request: "+err.Error(), http.StatusInternalServerError)
return
}
if s.Secret != "" && !s.verifyAuth(respBody, req) {
http.Error(w, "403 Forbidden - Verification of secret failed.", http.StatusForbidden)
return
}
go s.processPacket(EventType(eventType), respBody)
}
// ListenAndServe simply returns the error received from a call of
// http.ListenAndServe() with the correct parameters. Use this function if you
// want the call to block.
func (s *Server) ListenAndServe() error {
return http.ListenAndServe(fmt.Sprintf(":%v", s.Port), s)
}
// GoListenAndServe is a convenience function that runs Server.ListenAndServe()
// in a goroutine and panics upon receipt of an error. Use this function if
// you do not want the call to block.
func (s *Server) GoListenAndServe() {
go func() {
err := s.ListenAndServe()
if err != nil {
panic(err)
}
}()
}