...

Source file src/github.com/google/go-github/v55/github/messages_test.go

Documentation: github.com/google/go-github/v55/github

     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  }
   590  

View as plain text