github.com/google/go-github/v70@v70.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: &BranchProtectionConfigurationEvent{}, 269 messageType: "branch_protection_configuration", 270 }, 271 { 272 payload: &BranchProtectionRuleEvent{}, 273 messageType: "branch_protection_rule", 274 }, 275 { 276 payload: &CheckRunEvent{}, 277 messageType: "check_run", 278 }, 279 { 280 payload: &CheckSuiteEvent{}, 281 messageType: "check_suite", 282 }, 283 { 284 payload: &CodeScanningAlertEvent{}, 285 messageType: "code_scanning_alert", 286 }, 287 { 288 payload: &CommitCommentEvent{}, 289 messageType: "commit_comment", 290 }, 291 { 292 payload: &ContentReferenceEvent{}, 293 messageType: "content_reference", 294 }, 295 { 296 payload: &CreateEvent{}, 297 messageType: "create", 298 }, 299 { 300 payload: &CustomPropertyEvent{}, 301 messageType: "custom_property", 302 }, 303 { 304 payload: &CustomPropertyValuesEvent{}, 305 messageType: "custom_property_values", 306 }, 307 { 308 payload: &DeleteEvent{}, 309 messageType: "delete", 310 }, 311 { 312 payload: &DependabotAlertEvent{}, 313 messageType: "dependabot_alert", 314 }, 315 { 316 payload: &DeployKeyEvent{}, 317 messageType: "deploy_key", 318 }, 319 { 320 payload: &DeploymentEvent{}, 321 messageType: "deployment", 322 }, 323 { 324 payload: &DeploymentProtectionRuleEvent{}, 325 messageType: "deployment_protection_rule", 326 }, 327 { 328 payload: &DeploymentReviewEvent{}, 329 messageType: "deployment_review", 330 }, 331 { 332 payload: &DeploymentStatusEvent{}, 333 messageType: "deployment_status", 334 }, 335 { 336 payload: &DiscussionCommentEvent{}, 337 messageType: "discussion_comment", 338 }, 339 { 340 payload: &DiscussionEvent{}, 341 messageType: "discussion", 342 }, 343 { 344 payload: &ForkEvent{}, 345 messageType: "fork", 346 }, 347 { 348 payload: &GitHubAppAuthorizationEvent{}, 349 messageType: "github_app_authorization", 350 }, 351 { 352 payload: &GollumEvent{}, 353 messageType: "gollum", 354 }, 355 { 356 payload: &InstallationEvent{}, 357 messageType: "installation", 358 }, 359 { 360 payload: &InstallationRepositoriesEvent{}, 361 messageType: "installation_repositories", 362 }, 363 { 364 payload: &InstallationTargetEvent{}, 365 messageType: "installation_target", 366 }, 367 { 368 payload: &IssueCommentEvent{}, 369 messageType: "issue_comment", 370 }, 371 { 372 payload: &IssuesEvent{}, 373 messageType: "issues", 374 }, 375 { 376 payload: &LabelEvent{}, 377 messageType: "label", 378 }, 379 { 380 payload: &MarketplacePurchaseEvent{}, 381 messageType: "marketplace_purchase", 382 }, 383 { 384 payload: &MemberEvent{}, 385 messageType: "member", 386 }, 387 { 388 payload: &MembershipEvent{}, 389 messageType: "membership", 390 }, 391 { 392 payload: &MergeGroupEvent{}, 393 messageType: "merge_group", 394 }, 395 { 396 payload: &MetaEvent{}, 397 messageType: "meta", 398 }, 399 { 400 payload: &MilestoneEvent{}, 401 messageType: "milestone", 402 }, 403 { 404 payload: &OrganizationEvent{}, 405 messageType: "organization", 406 }, 407 { 408 payload: &OrgBlockEvent{}, 409 messageType: "org_block", 410 }, 411 { 412 payload: &PackageEvent{}, 413 messageType: "package", 414 }, 415 { 416 payload: &PageBuildEvent{}, 417 messageType: "page_build", 418 }, 419 { 420 payload: &PersonalAccessTokenRequestEvent{}, 421 messageType: "personal_access_token_request", 422 }, 423 { 424 payload: &PingEvent{}, 425 messageType: "ping", 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: &RepositoryRulesetEvent{}, 473 messageType: "repository_ruleset", 474 }, 475 { 476 payload: &RepositoryVulnerabilityAlertEvent{}, 477 messageType: "repository_vulnerability_alert", 478 }, 479 { 480 payload: &SecretScanningAlertEvent{}, 481 messageType: "secret_scanning_alert", 482 }, 483 { 484 payload: &SecretScanningAlertLocationEvent{}, 485 messageType: "secret_scanning_alert_location", 486 }, 487 { 488 payload: &SecurityAdvisoryEvent{}, 489 messageType: "security_advisory", 490 }, 491 { 492 payload: &SecurityAndAnalysisEvent{}, 493 messageType: "security_and_analysis", 494 }, 495 { 496 payload: &SponsorshipEvent{}, 497 messageType: "sponsorship", 498 }, 499 { 500 payload: &StarEvent{}, 501 messageType: "star", 502 }, 503 { 504 payload: &StatusEvent{}, 505 messageType: "status", 506 }, 507 { 508 payload: &TeamEvent{}, 509 messageType: "team", 510 }, 511 { 512 payload: &TeamAddEvent{}, 513 messageType: "team_add", 514 }, 515 { 516 payload: &UserEvent{}, 517 messageType: "user", 518 }, 519 { 520 payload: &WatchEvent{}, 521 messageType: "watch", 522 }, 523 { 524 payload: &RepositoryImportEvent{}, 525 messageType: "repository_import", 526 }, 527 { 528 payload: &RepositoryDispatchEvent{}, 529 messageType: "repository_dispatch", 530 }, 531 { 532 payload: &WorkflowDispatchEvent{}, 533 messageType: "workflow_dispatch", 534 }, 535 { 536 payload: &WorkflowJobEvent{}, 537 messageType: "workflow_job", 538 }, 539 { 540 payload: &WorkflowRunEvent{}, 541 messageType: "workflow_run", 542 }, 543 } 544 545 for _, test := range tests { 546 p, err := json.Marshal(test.payload) 547 if err != nil { 548 t.Fatalf("Marshal(%#v): %v", test.payload, err) 549 } 550 got, err := ParseWebHook(test.messageType, p) 551 if err != nil { 552 t.Fatalf("ParseWebHook: %v", err) 553 } 554 if want := test.payload; !cmp.Equal(got, want) { 555 t.Errorf("ParseWebHook(%#v, %#v) = %#v, want %#v", test.messageType, p, got, want) 556 } 557 } 558 } 559 560 func TestAllMessageTypesMapped(t *testing.T) { 561 t.Parallel() 562 for _, mt := range MessageTypes() { 563 if obj := EventForType(mt); obj == nil { 564 t.Errorf("messageMap missing message type %q", mt) 565 } 566 } 567 } 568 569 func TestUnknownMessageType(t *testing.T) { 570 t.Parallel() 571 if obj := EventForType("unknown"); obj != nil { 572 t.Errorf("EventForType(unknown) = %#v, want nil", obj) 573 } 574 if obj := EventForType(""); obj != nil { 575 t.Errorf(`EventForType("") = %#v, want nil`, obj) 576 } 577 } 578 579 func TestParseWebHook_BadMessageType(t *testing.T) { 580 t.Parallel() 581 if _, err := ParseWebHook("bogus message type", []byte("{}")); err == nil { 582 t.Fatal("ParseWebHook returned nil; wanted error") 583 } 584 } 585 586 func TestValidatePayloadFromBody_UnableToParseBody(t *testing.T) { 587 t.Parallel() 588 if _, err := ValidatePayloadFromBody("application/x-www-form-urlencoded", bytes.NewReader([]byte(`%`)), "sha1=", []byte{}); err == nil { 589 t.Errorf("ValidatePayloadFromBody returned nil; wanted error") 590 } 591 } 592 593 func TestValidatePayloadFromBody_UnsupportedContentType(t *testing.T) { 594 t.Parallel() 595 if _, err := ValidatePayloadFromBody("invalid", bytes.NewReader([]byte(`{}`)), "sha1=", []byte{}); err == nil { 596 t.Errorf("ValidatePayloadFromBody returned nil; wanted error") 597 } 598 } 599 600 func TestDeliveryID(t *testing.T) { 601 t.Parallel() 602 id := "8970a780-244e-11e7-91ca-da3aabcb9793" 603 req, err := http.NewRequest("POST", "http://localhost", nil) 604 if err != nil { 605 t.Fatalf("DeliveryID: %v", err) 606 } 607 req.Header.Set("X-Github-Delivery", id) 608 609 got := DeliveryID(req) 610 if got != id { 611 t.Errorf("DeliveryID(%#v) = %q, want %q", req, got, id) 612 } 613 } 614 615 func TestWebHookType(t *testing.T) { 616 t.Parallel() 617 want := "yo" 618 req := &http.Request{ 619 Header: http.Header{EventTypeHeader: []string{want}}, 620 } 621 if got := WebHookType(req); got != want { 622 t.Errorf("WebHookType = %q, want %q", got, want) 623 } 624 }