github.com/google/go-github/v71@v71.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 t.Run(fmt.Sprintf("test #%v", i), func(t *testing.T) { 219 t.Parallel() 220 req := &http.Request{ 221 Header: http.Header{"Content-Type": []string{tt.contentType}}, 222 Body: &badReader{}, 223 } 224 if _, err := ValidatePayload(req, nil); err == nil { 225 t.Fatal("ValidatePayload returned nil; want error") 226 } 227 }) 228 } 229 } 230 231 func TestValidatePayload_InvalidContentTypeParams(t *testing.T) { 232 t.Parallel() 233 req, err := http.NewRequest("POST", "http://localhost/event", nil) 234 if err != nil { 235 t.Fatalf("NewRequest: %v", err) 236 } 237 req.Header.Set("Content-Type", "application/json; charset=") 238 if _, err = ValidatePayload(req, nil); err == nil { 239 t.Error("ValidatePayload = nil, want err") 240 } 241 } 242 243 func TestValidatePayload_ValidContentTypeParams(t *testing.T) { 244 t.Parallel() 245 var requestBody = `{"yo":true}` 246 buf := bytes.NewBufferString(requestBody) 247 248 req, err := http.NewRequest("POST", "http://localhost/event", buf) 249 if err != nil { 250 t.Fatalf("NewRequest: %v", err) 251 } 252 req.Header.Set("Content-Type", "application/json; charset=UTF-8") 253 254 _, err = ValidatePayload(req, nil) 255 if err != nil { 256 t.Error("ValidatePayload = nil, want err") 257 } 258 } 259 260 func TestParseWebHook(t *testing.T) { 261 t.Parallel() 262 tests := []struct { 263 payload interface{} 264 messageType string 265 }{ 266 { 267 payload: &BranchProtectionConfigurationEvent{}, 268 messageType: "branch_protection_configuration", 269 }, 270 { 271 payload: &BranchProtectionRuleEvent{}, 272 messageType: "branch_protection_rule", 273 }, 274 { 275 payload: &CheckRunEvent{}, 276 messageType: "check_run", 277 }, 278 { 279 payload: &CheckSuiteEvent{}, 280 messageType: "check_suite", 281 }, 282 { 283 payload: &CodeScanningAlertEvent{}, 284 messageType: "code_scanning_alert", 285 }, 286 { 287 payload: &CommitCommentEvent{}, 288 messageType: "commit_comment", 289 }, 290 { 291 payload: &ContentReferenceEvent{}, 292 messageType: "content_reference", 293 }, 294 { 295 payload: &CreateEvent{}, 296 messageType: "create", 297 }, 298 { 299 payload: &CustomPropertyEvent{}, 300 messageType: "custom_property", 301 }, 302 { 303 payload: &CustomPropertyValuesEvent{}, 304 messageType: "custom_property_values", 305 }, 306 { 307 payload: &DeleteEvent{}, 308 messageType: "delete", 309 }, 310 { 311 payload: &DependabotAlertEvent{}, 312 messageType: "dependabot_alert", 313 }, 314 { 315 payload: &DeployKeyEvent{}, 316 messageType: "deploy_key", 317 }, 318 { 319 payload: &DeploymentEvent{}, 320 messageType: "deployment", 321 }, 322 { 323 payload: &DeploymentProtectionRuleEvent{}, 324 messageType: "deployment_protection_rule", 325 }, 326 { 327 payload: &DeploymentReviewEvent{}, 328 messageType: "deployment_review", 329 }, 330 { 331 payload: &DeploymentStatusEvent{}, 332 messageType: "deployment_status", 333 }, 334 { 335 payload: &DiscussionCommentEvent{}, 336 messageType: "discussion_comment", 337 }, 338 { 339 payload: &DiscussionEvent{}, 340 messageType: "discussion", 341 }, 342 { 343 payload: &ForkEvent{}, 344 messageType: "fork", 345 }, 346 { 347 payload: &GitHubAppAuthorizationEvent{}, 348 messageType: "github_app_authorization", 349 }, 350 { 351 payload: &GollumEvent{}, 352 messageType: "gollum", 353 }, 354 { 355 payload: &InstallationEvent{}, 356 messageType: "installation", 357 }, 358 { 359 payload: &InstallationRepositoriesEvent{}, 360 messageType: "installation_repositories", 361 }, 362 { 363 payload: &InstallationTargetEvent{}, 364 messageType: "installation_target", 365 }, 366 { 367 payload: &IssueCommentEvent{}, 368 messageType: "issue_comment", 369 }, 370 { 371 payload: &IssuesEvent{}, 372 messageType: "issues", 373 }, 374 { 375 payload: &LabelEvent{}, 376 messageType: "label", 377 }, 378 { 379 payload: &MarketplacePurchaseEvent{}, 380 messageType: "marketplace_purchase", 381 }, 382 { 383 payload: &MemberEvent{}, 384 messageType: "member", 385 }, 386 { 387 payload: &MembershipEvent{}, 388 messageType: "membership", 389 }, 390 { 391 payload: &MergeGroupEvent{}, 392 messageType: "merge_group", 393 }, 394 { 395 payload: &MetaEvent{}, 396 messageType: "meta", 397 }, 398 { 399 payload: &MilestoneEvent{}, 400 messageType: "milestone", 401 }, 402 { 403 payload: &OrganizationEvent{}, 404 messageType: "organization", 405 }, 406 { 407 payload: &OrgBlockEvent{}, 408 messageType: "org_block", 409 }, 410 { 411 payload: &PackageEvent{}, 412 messageType: "package", 413 }, 414 { 415 payload: &PageBuildEvent{}, 416 messageType: "page_build", 417 }, 418 { 419 payload: &PersonalAccessTokenRequestEvent{}, 420 messageType: "personal_access_token_request", 421 }, 422 { 423 payload: &PingEvent{}, 424 messageType: "ping", 425 }, 426 { 427 payload: &ProjectV2Event{}, 428 messageType: "projects_v2", 429 }, 430 { 431 payload: &ProjectV2ItemEvent{}, 432 messageType: "projects_v2_item", 433 }, 434 { 435 payload: &PublicEvent{}, 436 messageType: "public", 437 }, 438 { 439 payload: &PullRequestEvent{}, 440 messageType: "pull_request", 441 }, 442 { 443 payload: &PullRequestReviewEvent{}, 444 messageType: "pull_request_review", 445 }, 446 { 447 payload: &PullRequestReviewCommentEvent{}, 448 messageType: "pull_request_review_comment", 449 }, 450 { 451 payload: &PullRequestReviewThreadEvent{}, 452 messageType: "pull_request_review_thread", 453 }, 454 { 455 payload: &PullRequestTargetEvent{}, 456 messageType: "pull_request_target", 457 }, 458 { 459 payload: &PushEvent{}, 460 messageType: "push", 461 }, 462 { 463 payload: &ReleaseEvent{}, 464 messageType: "release", 465 }, 466 { 467 payload: &RepositoryEvent{}, 468 messageType: "repository", 469 }, 470 { 471 payload: &RepositoryRulesetEvent{}, 472 messageType: "repository_ruleset", 473 }, 474 { 475 payload: &RepositoryVulnerabilityAlertEvent{}, 476 messageType: "repository_vulnerability_alert", 477 }, 478 { 479 payload: &SecretScanningAlertEvent{}, 480 messageType: "secret_scanning_alert", 481 }, 482 { 483 payload: &SecretScanningAlertLocationEvent{}, 484 messageType: "secret_scanning_alert_location", 485 }, 486 { 487 payload: &SecurityAdvisoryEvent{}, 488 messageType: "security_advisory", 489 }, 490 { 491 payload: &SecurityAndAnalysisEvent{}, 492 messageType: "security_and_analysis", 493 }, 494 { 495 payload: &SponsorshipEvent{}, 496 messageType: "sponsorship", 497 }, 498 { 499 payload: &StarEvent{}, 500 messageType: "star", 501 }, 502 { 503 payload: &StatusEvent{}, 504 messageType: "status", 505 }, 506 { 507 payload: &TeamEvent{}, 508 messageType: "team", 509 }, 510 { 511 payload: &TeamAddEvent{}, 512 messageType: "team_add", 513 }, 514 { 515 payload: &UserEvent{}, 516 messageType: "user", 517 }, 518 { 519 payload: &WatchEvent{}, 520 messageType: "watch", 521 }, 522 { 523 payload: &RepositoryImportEvent{}, 524 messageType: "repository_import", 525 }, 526 { 527 payload: &RepositoryDispatchEvent{}, 528 messageType: "repository_dispatch", 529 }, 530 { 531 payload: &WorkflowDispatchEvent{}, 532 messageType: "workflow_dispatch", 533 }, 534 { 535 payload: &WorkflowJobEvent{}, 536 messageType: "workflow_job", 537 }, 538 { 539 payload: &WorkflowRunEvent{}, 540 messageType: "workflow_run", 541 }, 542 } 543 544 for _, test := range tests { 545 p, err := json.Marshal(test.payload) 546 if err != nil { 547 t.Fatalf("Marshal(%#v): %v", test.payload, err) 548 } 549 got, err := ParseWebHook(test.messageType, p) 550 if err != nil { 551 t.Fatalf("ParseWebHook: %v", err) 552 } 553 if want := test.payload; !cmp.Equal(got, want) { 554 t.Errorf("ParseWebHook(%#v, %#v) = %#v, want %#v", test.messageType, p, got, want) 555 } 556 } 557 } 558 559 func TestAllMessageTypesMapped(t *testing.T) { 560 t.Parallel() 561 for _, mt := range MessageTypes() { 562 if obj := EventForType(mt); obj == nil { 563 t.Errorf("messageMap missing message type %q", mt) 564 } 565 } 566 } 567 568 func TestUnknownMessageType(t *testing.T) { 569 t.Parallel() 570 if obj := EventForType("unknown"); obj != nil { 571 t.Errorf("EventForType(unknown) = %#v, want nil", obj) 572 } 573 if obj := EventForType(""); obj != nil { 574 t.Errorf(`EventForType("") = %#v, want nil`, obj) 575 } 576 } 577 578 func TestParseWebHook_BadMessageType(t *testing.T) { 579 t.Parallel() 580 if _, err := ParseWebHook("bogus message type", []byte("{}")); err == nil { 581 t.Fatal("ParseWebHook returned nil; wanted error") 582 } 583 } 584 585 func TestValidatePayloadFromBody_UnableToParseBody(t *testing.T) { 586 t.Parallel() 587 if _, err := ValidatePayloadFromBody("application/x-www-form-urlencoded", bytes.NewReader([]byte(`%`)), "sha1=", []byte{}); err == nil { 588 t.Errorf("ValidatePayloadFromBody returned nil; wanted error") 589 } 590 } 591 592 func TestValidatePayloadFromBody_UnsupportedContentType(t *testing.T) { 593 t.Parallel() 594 if _, err := ValidatePayloadFromBody("invalid", bytes.NewReader([]byte(`{}`)), "sha1=", []byte{}); err == nil { 595 t.Errorf("ValidatePayloadFromBody returned nil; wanted error") 596 } 597 } 598 599 func TestDeliveryID(t *testing.T) { 600 t.Parallel() 601 id := "8970a780-244e-11e7-91ca-da3aabcb9793" 602 req, err := http.NewRequest("POST", "http://localhost", nil) 603 if err != nil { 604 t.Fatalf("DeliveryID: %v", err) 605 } 606 req.Header.Set("X-Github-Delivery", id) 607 608 got := DeliveryID(req) 609 if got != id { 610 t.Errorf("DeliveryID(%#v) = %q, want %q", req, got, id) 611 } 612 } 613 614 func TestWebHookType(t *testing.T) { 615 t.Parallel() 616 want := "yo" 617 req := &http.Request{ 618 Header: http.Header{EventTypeHeader: []string{want}}, 619 } 620 if got := WebHookType(req); got != want { 621 t.Errorf("WebHookType = %q, want %q", got, want) 622 } 623 }