github.com/google/go-github/v60@v60.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 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}` // All tests below use the default request body and signature. 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 // The following tests generate expected errors: 39 {secretKey: secretKey}, // Missing signature 40 {secretKey: secretKey, signature: "yo"}, // Missing signature prefix 41 {secretKey: secretKey, signature: "sha1=yo"}, // Signature not hex string 42 {secretKey: secretKey, signature: "sha1=012345"}, // Invalid signature 43 {signature: defaultSignature}, // signature without secretKey 44 45 // The following tests expect err=nil: 46 { 47 // no secretKey and no signature still passes validation 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 // check that if payload is invalid we get error 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 // check that if payload is invalid we get error 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 // badReader satisfies io.Reader but always returns an error. 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 }