github.com/google/go-github/v69@v69.2.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  }