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