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