1
2
3
4
5
6 package github
7
8 import (
9 "bytes"
10 "encoding/json"
11 "errors"
12 "fmt"
13 "net/http"
14 "net/url"
15 "strings"
16 "testing"
17
18 "github.com/google/go-cmp/cmp"
19 )
20
21 func TestMessageMAC_BadHashTypePrefix(t *testing.T) {
22 const signature = "bogus1=1234567"
23 if _, _, err := messageMAC(signature); err == nil {
24 t.Fatal("messageMAC returned nil; wanted error")
25 }
26 }
27
28 func TestValidatePayload(t *testing.T) {
29 const defaultBody = `{"yo":true}`
30 const defaultSignature = "sha1=126f2c800419c60137ce748d7672e77b65cf16d6"
31 secretKey := []byte("0123456789abcdef")
32 tests := []struct {
33 secretKey []byte
34 signature string
35 signatureHeader string
36 wantPayload string
37 }{
38
39 {secretKey: secretKey},
40 {secretKey: secretKey, signature: "yo"},
41 {secretKey: secretKey, signature: "sha1=yo"},
42 {secretKey: secretKey, signature: "sha1=012345"},
43 {signature: defaultSignature},
44
45
46 {
47
48 wantPayload: defaultBody,
49 },
50 {
51 secretKey: secretKey,
52 signature: defaultSignature,
53 wantPayload: defaultBody,
54 },
55 {
56 secretKey: secretKey,
57 signature: "sha256=b1f8020f5b4cd42042f807dd939015c4a418bc1ff7f604dd55b0a19b5d953d9b",
58 wantPayload: defaultBody,
59 },
60 {
61 secretKey: secretKey,
62 signature: "sha256=b1f8020f5b4cd42042f807dd939015c4a418bc1ff7f604dd55b0a19b5d953d9b",
63 signatureHeader: SHA256SignatureHeader,
64 wantPayload: defaultBody,
65 },
66 {
67 secretKey: secretKey,
68 signature: "sha512=8456767023c1195682e182a23b3f5d19150ecea598fde8cb85918f7281b16079471b1329f92b912c4d8bd7455cb159777db8f29608b20c7c87323ba65ae62e1f",
69 wantPayload: defaultBody,
70 },
71 }
72
73 for _, test := range tests {
74 buf := bytes.NewBufferString(defaultBody)
75 req, err := http.NewRequest("GET", "http://localhost/event", buf)
76 if err != nil {
77 t.Fatalf("NewRequest: %v", err)
78 }
79 if test.signature != "" {
80 if test.signatureHeader != "" {
81 req.Header.Set(test.signatureHeader, test.signature)
82 } else {
83 req.Header.Set(SHA1SignatureHeader, test.signature)
84 }
85 }
86 req.Header.Set("Content-Type", "application/json")
87
88 got, err := ValidatePayload(req, test.secretKey)
89 if err != nil {
90 if test.wantPayload != "" {
91 t.Errorf("ValidatePayload(%#v): err = %v, want nil", test, err)
92 }
93 continue
94 }
95 if string(got) != test.wantPayload {
96 t.Errorf("ValidatePayload = %q, want %q", got, test.wantPayload)
97 }
98 }
99 }
100
101 func TestValidatePayload_FormGet(t *testing.T) {
102 payload := `{"yo":true}`
103 signature := "sha1=3374ef144403e8035423b23b02e2c9d7a4c50368"
104 secretKey := []byte("0123456789abcdef")
105
106 form := url.Values{}
107 form.Add("payload", payload)
108 req, err := http.NewRequest("POST", "http://localhost/event", strings.NewReader(form.Encode()))
109 if err != nil {
110 t.Fatalf("NewRequest: %v", err)
111 }
112 req.PostForm = form
113 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
114 req.Header.Set(SHA1SignatureHeader, signature)
115
116 got, err := ValidatePayload(req, secretKey)
117 if err != nil {
118 t.Errorf("ValidatePayload(%#v): err = %v, want nil", payload, err)
119 }
120 if string(got) != payload {
121 t.Errorf("ValidatePayload = %q, want %q", got, payload)
122 }
123
124
125 req.Header.Set(SHA1SignatureHeader, "invalid signature")
126 if _, err = ValidatePayload(req, []byte{0}); err == nil {
127 t.Error("ValidatePayload = nil, want err")
128 }
129 }
130
131 func TestValidatePayload_FormPost(t *testing.T) {
132 payload := `{"yo":true}`
133 signature := "sha1=3374ef144403e8035423b23b02e2c9d7a4c50368"
134 secretKey := []byte("0123456789abcdef")
135
136 form := url.Values{}
137 form.Set("payload", payload)
138 buf := bytes.NewBufferString(form.Encode())
139 req, err := http.NewRequest("POST", "http://localhost/event", buf)
140 if err != nil {
141 t.Fatalf("NewRequest: %v", err)
142 }
143 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
144 req.Header.Set(SHA1SignatureHeader, signature)
145
146 got, err := ValidatePayload(req, secretKey)
147 if err != nil {
148 t.Errorf("ValidatePayload(%#v): err = %v, want nil", payload, err)
149 }
150 if string(got) != payload {
151 t.Errorf("ValidatePayload = %q, want %q", got, payload)
152 }
153
154
155 req.Header.Set(SHA1SignatureHeader, "invalid signature")
156 if _, err = ValidatePayload(req, []byte{0}); err == nil {
157 t.Error("ValidatePayload = nil, want err")
158 }
159 }
160
161 func TestValidatePayload_InvalidContentType(t *testing.T) {
162 req, err := http.NewRequest("POST", "http://localhost/event", nil)
163 if err != nil {
164 t.Fatalf("NewRequest: %v", err)
165 }
166 req.Header.Set("Content-Type", "invalid content type")
167 if _, err = ValidatePayload(req, nil); err == nil {
168 t.Error("ValidatePayload = nil, want err")
169 }
170 }
171
172 func TestValidatePayload_NoSecretKey(t *testing.T) {
173 payload := `{"yo":true}`
174
175 form := url.Values{}
176 form.Set("payload", payload)
177 buf := bytes.NewBufferString(form.Encode())
178 req, err := http.NewRequest("POST", "http://localhost/event", buf)
179 if err != nil {
180 t.Fatalf("NewRequest: %v", err)
181 }
182 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
183
184 got, err := ValidatePayload(req, nil)
185 if err != nil {
186 t.Errorf("ValidatePayload(%#v): err = %v, want nil", payload, err)
187 }
188 if string(got) != payload {
189 t.Errorf("ValidatePayload = %q, want %q", got, payload)
190 }
191 }
192
193
194 type badReader struct{}
195
196 func (b *badReader) Read(p []byte) (int, error) {
197 return 0, errors.New("bad reader")
198 }
199
200 func (b *badReader) Close() error { return errors.New("bad reader") }
201
202 func TestValidatePayload_BadRequestBody(t *testing.T) {
203 tests := []struct {
204 contentType string
205 }{
206 {contentType: "application/json"},
207 {contentType: "application/x-www-form-urlencoded"},
208 }
209
210 for i, tt := range tests {
211 t.Run(fmt.Sprintf("test #%v", i), func(t *testing.T) {
212 req := &http.Request{
213 Header: http.Header{"Content-Type": []string{tt.contentType}},
214 Body: &badReader{},
215 }
216 if _, err := ValidatePayload(req, nil); err == nil {
217 t.Fatal("ValidatePayload returned nil; want error")
218 }
219 })
220 }
221 }
222
223 func TestValidatePayload_InvalidContentTypeParams(t *testing.T) {
224 req, err := http.NewRequest("POST", "http://localhost/event", nil)
225 if err != nil {
226 t.Fatalf("NewRequest: %v", err)
227 }
228 req.Header.Set("Content-Type", "application/json; charset=")
229 if _, err = ValidatePayload(req, nil); err == nil {
230 t.Error("ValidatePayload = nil, want err")
231 }
232 }
233
234 func TestValidatePayload_ValidContentTypeParams(t *testing.T) {
235 var requestBody = `{"yo":true}`
236 buf := bytes.NewBufferString(requestBody)
237
238 req, err := http.NewRequest("POST", "http://localhost/event", buf)
239 if err != nil {
240 t.Fatalf("NewRequest: %v", err)
241 }
242 req.Header.Set("Content-Type", "application/json; charset=UTF-8")
243
244 _, err = ValidatePayload(req, nil)
245 if err != nil {
246 t.Error("ValidatePayload = nil, want err")
247 }
248 }
249
250 func TestParseWebHook(t *testing.T) {
251 tests := []struct {
252 payload interface{}
253 messageType string
254 }{
255 {
256 payload: &BranchProtectionRuleEvent{},
257 messageType: "branch_protection_rule",
258 },
259 {
260 payload: &CheckRunEvent{},
261 messageType: "check_run",
262 },
263 {
264 payload: &CheckSuiteEvent{},
265 messageType: "check_suite",
266 },
267 {
268 payload: &CodeScanningAlertEvent{},
269 messageType: "code_scanning_alert",
270 },
271 {
272 payload: &CommitCommentEvent{},
273 messageType: "commit_comment",
274 },
275 {
276 payload: &ContentReferenceEvent{},
277 messageType: "content_reference",
278 },
279 {
280 payload: &CreateEvent{},
281 messageType: "create",
282 },
283 {
284 payload: &DeleteEvent{},
285 messageType: "delete",
286 },
287 {
288 payload: &DependabotAlertEvent{},
289 messageType: "dependabot_alert",
290 },
291 {
292 payload: &DeployKeyEvent{},
293 messageType: "deploy_key",
294 },
295 {
296 payload: &DeploymentEvent{},
297 messageType: "deployment",
298 },
299 {
300 payload: &DeploymentProtectionRuleEvent{},
301 messageType: "deployment_protection_rule",
302 },
303 {
304 payload: &DeploymentStatusEvent{},
305 messageType: "deployment_status",
306 },
307 {
308 payload: &DiscussionCommentEvent{},
309 messageType: "discussion_comment",
310 },
311 {
312 payload: &DiscussionEvent{},
313 messageType: "discussion",
314 },
315 {
316 payload: &ForkEvent{},
317 messageType: "fork",
318 },
319 {
320 payload: &GitHubAppAuthorizationEvent{},
321 messageType: "github_app_authorization",
322 },
323 {
324 payload: &GollumEvent{},
325 messageType: "gollum",
326 },
327 {
328 payload: &InstallationEvent{},
329 messageType: "installation",
330 },
331 {
332 payload: &InstallationRepositoriesEvent{},
333 messageType: "installation_repositories",
334 },
335 {
336 payload: &InstallationTargetEvent{},
337 messageType: "installation_target",
338 },
339 {
340 payload: &IssueCommentEvent{},
341 messageType: "issue_comment",
342 },
343 {
344 payload: &IssuesEvent{},
345 messageType: "issues",
346 },
347 {
348 payload: &LabelEvent{},
349 messageType: "label",
350 },
351 {
352 payload: &MarketplacePurchaseEvent{},
353 messageType: "marketplace_purchase",
354 },
355 {
356 payload: &MemberEvent{},
357 messageType: "member",
358 },
359 {
360 payload: &MembershipEvent{},
361 messageType: "membership",
362 },
363 {
364 payload: &MergeGroupEvent{},
365 messageType: "merge_group",
366 },
367 {
368 payload: &MetaEvent{},
369 messageType: "meta",
370 },
371 {
372 payload: &MilestoneEvent{},
373 messageType: "milestone",
374 },
375 {
376 payload: &OrganizationEvent{},
377 messageType: "organization",
378 },
379 {
380 payload: &OrgBlockEvent{},
381 messageType: "org_block",
382 },
383 {
384 payload: &PackageEvent{},
385 messageType: "package",
386 },
387 {
388 payload: &PageBuildEvent{},
389 messageType: "page_build",
390 },
391 {
392 payload: &PersonalAccessTokenRequestEvent{},
393 messageType: "personal_access_token_request",
394 },
395 {
396 payload: &PingEvent{},
397 messageType: "ping",
398 },
399 {
400 payload: &ProjectEvent{},
401 messageType: "project",
402 },
403 {
404 payload: &ProjectCardEvent{},
405 messageType: "project_card",
406 },
407 {
408 payload: &ProjectColumnEvent{},
409 messageType: "project_column",
410 },
411 {
412 payload: &ProjectV2Event{},
413 messageType: "projects_v2",
414 },
415 {
416 payload: &ProjectV2ItemEvent{},
417 messageType: "projects_v2_item",
418 },
419 {
420 payload: &PublicEvent{},
421 messageType: "public",
422 },
423 {
424 payload: &PullRequestEvent{},
425 messageType: "pull_request",
426 },
427 {
428 payload: &PullRequestReviewEvent{},
429 messageType: "pull_request_review",
430 },
431 {
432 payload: &PullRequestReviewCommentEvent{},
433 messageType: "pull_request_review_comment",
434 },
435 {
436 payload: &PullRequestReviewThreadEvent{},
437 messageType: "pull_request_review_thread",
438 },
439 {
440 payload: &PullRequestTargetEvent{},
441 messageType: "pull_request_target",
442 },
443 {
444 payload: &PushEvent{},
445 messageType: "push",
446 },
447 {
448 payload: &ReleaseEvent{},
449 messageType: "release",
450 },
451 {
452 payload: &RepositoryEvent{},
453 messageType: "repository",
454 },
455 {
456 payload: &RepositoryVulnerabilityAlertEvent{},
457 messageType: "repository_vulnerability_alert",
458 },
459 {
460 payload: &SecretScanningAlertEvent{},
461 messageType: "secret_scanning_alert",
462 },
463 {
464 payload: &SecurityAdvisoryEvent{},
465 messageType: "security_advisory",
466 },
467 {
468 payload: &SecurityAndAnalysisEvent{},
469 messageType: "security_and_analysis",
470 },
471 {
472 payload: &StarEvent{},
473 messageType: "star",
474 },
475 {
476 payload: &StatusEvent{},
477 messageType: "status",
478 },
479 {
480 payload: &TeamEvent{},
481 messageType: "team",
482 },
483 {
484 payload: &TeamAddEvent{},
485 messageType: "team_add",
486 },
487 {
488 payload: &UserEvent{},
489 messageType: "user",
490 },
491 {
492 payload: &WatchEvent{},
493 messageType: "watch",
494 },
495 {
496 payload: &RepositoryImportEvent{},
497 messageType: "repository_import",
498 },
499 {
500 payload: &RepositoryDispatchEvent{},
501 messageType: "repository_dispatch",
502 },
503 {
504 payload: &WorkflowDispatchEvent{},
505 messageType: "workflow_dispatch",
506 },
507 {
508 payload: &WorkflowJobEvent{},
509 messageType: "workflow_job",
510 },
511 {
512 payload: &WorkflowRunEvent{},
513 messageType: "workflow_run",
514 },
515 }
516
517 for _, test := range tests {
518 p, err := json.Marshal(test.payload)
519 if err != nil {
520 t.Fatalf("Marshal(%#v): %v", test.payload, err)
521 }
522 got, err := ParseWebHook(test.messageType, p)
523 if err != nil {
524 t.Fatalf("ParseWebHook: %v", err)
525 }
526 if want := test.payload; !cmp.Equal(got, want) {
527 t.Errorf("ParseWebHook(%#v, %#v) = %#v, want %#v", test.messageType, p, got, want)
528 }
529 }
530 }
531
532 func TestAllMessageTypesMapped(t *testing.T) {
533 for _, mt := range MessageTypes() {
534 if obj := EventForType(mt); obj == nil {
535 t.Errorf("messageMap missing message type %q", mt)
536 }
537 }
538 }
539
540 func TestUnknownMessageType(t *testing.T) {
541 if obj := EventForType("unknown"); obj != nil {
542 t.Errorf("EventForType(unknown) = %#v, want nil", obj)
543 }
544 if obj := EventForType(""); obj != nil {
545 t.Errorf(`EventForType("") = %#v, want nil`, obj)
546 }
547 }
548
549 func TestParseWebHook_BadMessageType(t *testing.T) {
550 if _, err := ParseWebHook("bogus message type", []byte("{}")); err == nil {
551 t.Fatal("ParseWebHook returned nil; wanted error")
552 }
553 }
554
555 func TestValidatePayloadFromBody_UnableToParseBody(t *testing.T) {
556 if _, err := ValidatePayloadFromBody("application/x-www-form-urlencoded", bytes.NewReader([]byte(`%`)), "sha1=", []byte{}); err == nil {
557 t.Errorf("ValidatePayloadFromBody returned nil; wanted error")
558 }
559 }
560
561 func TestValidatePayloadFromBody_UnsupportedContentType(t *testing.T) {
562 if _, err := ValidatePayloadFromBody("invalid", bytes.NewReader([]byte(`{}`)), "sha1=", []byte{}); err == nil {
563 t.Errorf("ValidatePayloadFromBody returned nil; wanted error")
564 }
565 }
566
567 func TestDeliveryID(t *testing.T) {
568 id := "8970a780-244e-11e7-91ca-da3aabcb9793"
569 req, err := http.NewRequest("POST", "http://localhost", nil)
570 if err != nil {
571 t.Fatalf("DeliveryID: %v", err)
572 }
573 req.Header.Set("X-Github-Delivery", id)
574
575 got := DeliveryID(req)
576 if got != id {
577 t.Errorf("DeliveryID(%#v) = %q, want %q", req, got, id)
578 }
579 }
580
581 func TestWebHookType(t *testing.T) {
582 want := "yo"
583 req := &http.Request{
584 Header: http.Header{EventTypeHeader: []string{want}},
585 }
586 if got := WebHookType(req); got != want {
587 t.Errorf("WebHookType = %q, want %q", got, want)
588 }
589 }
590
View as plain text