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  }