From edb77477c0622e586baa391c939835ed13825a4d Mon Sep 17 00:00:00 2001 From: Torben Neufeldt Date: Sat, 20 Jun 2020 20:21:25 +0200 Subject: [PATCH 1/7] Add Github App authentication --- authentication.go | 65 +++++++++++++++++++++++++++++++++++++++ authentication_test.go | 69 ++++++++++++++++++++++++++++++++++++++++++ git.go | 7 ++++- github.go | 7 ++++- go.mod | 1 + go.sum | 2 ++ models.go | 7 +++-- 7 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 authentication.go create mode 100644 authentication_test.go diff --git a/authentication.go b/authentication.go new file mode 100644 index 00000000..58afc767 --- /dev/null +++ b/authentication.go @@ -0,0 +1,65 @@ +package resource + +import ( + "crypto/x509" + "encoding/json" + "encoding/pem" + "gopkg.in/square/go-jose.v2" + "gopkg.in/square/go-jose.v2/jwt" + "net/http" + "time" +) + +type Response struct { + Token string +} + +func GenerateAccessToken(s *Source, now time.Time) (string, error) { + if s.AccessToken != "" { + return s.AccessToken, nil + } + + decode, _ := pem.Decode([]byte(s.PrivateKey)) + key, _ := x509.ParsePKCS1PrivateKey(decode.Bytes) + sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: key}, (&jose.SignerOptions{}).WithType("JWT")) + if err != nil { + panic(err) + } + + cl := jwt.Claims{ + Issuer: s.AppId, + IssuedAt: jwt.NewNumericDate(now), + Expiry: jwt.NewNumericDate(now.Add(9 * time.Minute)), + } + signedJwt, err := jwt.Signed(sig).Claims(cl).CompactSerialize() + if err != nil { + panic(err) + } + + var endpoint string + if s.V3Endpoint != "" { + endpoint = s.V3Endpoint + } else { + endpoint = "https://api.github.com" + } + request, err := http.NewRequest("POST", endpoint+"/app/installations/"+s.InstallationId+"/access_tokens", nil) + if err != nil { + panic(err) + } + request.Header.Add("Authorization", "Bearer "+signedJwt) + request.Header.Add("Accept", "application/vnd.github.machine-man-preview+json") + client := &http.Client{} + response, err := client.Do(request) + + if err != nil { + panic(err) + } + + var r Response + err = json.NewDecoder(response.Body).Decode(&r) + if err != nil { + panic(err) + } + + return r.Token, nil +} diff --git a/authentication_test.go b/authentication_test.go new file mode 100644 index 00000000..95c9ecef --- /dev/null +++ b/authentication_test.go @@ -0,0 +1,69 @@ +package resource_test + +import ( + "fmt" + "github.com/stretchr/testify/assert" + resource "github.com/telia-oss/github-pr-resource" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestGenerateAccessToken(t *testing.T) { + + validResponse := ` +{ + "token": "v1.b71be873ad96e64a84025ae7bee7694a99cb4ba9", + "expires_at": "2020-06-21T00:03:29Z", + "permissions": { + "checks": "write", + "contents": "read", + "metadata": "read", + "pull_requests": "write" + }, + "repository_selection": "selected" +} +` + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/app/installations/9912873/access_tokens", r.RequestURI) + assert.Equal(t, "application/vnd.github.machine-man-preview+json", r.Header.Get("Accept")) + assert.Equal(t, "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTI2OTE5ODIsImlhdCI6MTU5MjY5MTQ0MiwiaXNzIjoiNjk1OTIifQ.H_a6i7TpaGOsaoliH_i7AT5UMwM9LqO21lEFYiZ96_H15cEF6D_kyZrcHyinP2fSC8rX_OQ-DvPsehTNTtfOhgM-nsdgg-gTzdCSlASgc00sGhw_pjBFwHpD6V1NQojc82L8SAR9Bg75g0xVlQ_dAR_Lbtmk252X_AlabRAfdSchK_GVdc3kSEbp28lc87EF7J_lFdRCNuVi1xcLFOXPmMeu4epSBf1ZMtuts28C7iqaI4QJ9keaGFug1wpL-WLDcFbvmB2nJhBYN9tArGM0ZHZ5i4EhFyFjGpBwTyo5P7WY7P3zYtz36gwgntYRtPPivcFQ-wUWuvMpL6vKd-Pp8w", r.Header.Get("Authorization")) + _, _ = fmt.Fprintln(w, validResponse) + })) + defer ts.Close() + + tests := []struct { + description string + source resource.Source + expectedAccessToken string + }{ + { + description: "return given access token", + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + }, + expectedAccessToken: "oauthtoken", + }, + { + description: "create access token", + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AppId: "69592", + PrivateKey: "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAv6oJaa+0BCOT0dITtJK6oPfrE2R0Iynofj/a/vq4rfAw2MJp\nXA5l6WbwmQhevSm8sIYNb2T32qLyRsVXwBo1J8ovIoTXiPdsV41D19tytjctnf+u\n0LncTo2JJR3ik7/Ynu5Id4zjnsn1pYqwLX4EFxgCuEKwdZutPyiY2J7wATfsTtOJ\nsLF8idQijG23i5Obs6AWCZcHOhvdgfYAUOxLv2WRkCG5O1aXYa6nqVn0AgRgKjaJ\nnAENEoG7O9OEWIcUiG30riQouxMHfj0bATCvYoj7a2tvn4CUqk+SBODyh3Fvi0oC\na/NCBYFeK/5uUNyXQHqWy7xEVRef5lC0XbrUVwIDAQABAoIBAFDdqhkIQ/iXFjAp\n5ZyDZ/CwiWNmN8X6UZiq0nhQSolA1SsvY4qunHsMrqiyql4/dNg5xwNf419A7t3D\nN5HavOCr4pU63UFxuyl5dc1mTpDo2PtXvGdec8BE4T9iy40xHXF48eRW8la1uUn+\nKPUYvRsNS2B46sDETSVfuJV1AahRN6aD2WnzQ7wB+S/mqsPXqy+S2zobnU70Wzmt\nhYQzsuIY+BkfOCS565guYvJt66wRGi/NsnybC6z3iRZxZygtKorMKXPjmtdoyCZ3\njkOHhXV5XH8Ldut+1mycg+6c+dTZ9RTrAzo4ouofptm9+ZNlv1aK7HrDDKYQ/x/z\n70hhIfkCgYEA7aNt7db3S6sTEA+yC5DLxxkFIK7K8qxdP6vE087fdHO+ik1LvyI+\ni5Wqj/fT2d/lYR+cbH/zy2JBy5RWfdVJ9HBW/eZ9RqQx8ry7lE5MqbwU6y+d2bCF\nAjffx3yJK1aljuVLdGeu8abYsusUQUhbslZJNg6Ar7z+FUHaChJI0KsCgYEAznk4\nx+5PtvIWj6aXTJiqtofnOtHXXxrq5alzBErvDBHHzP9/ZCucHxJZ4zqHWduj2+9b\nJUgk2BH3+wFMVKpcdTld2iTFWGaFsArTJkE4SBQGx3zcdsoESOnu/DrOTFNu+LiV\nhLzb9CHKM91fIet8MYYxKiyH09+Mi7xBtw8WQwUCgYAH/tCrCOmHHTll9/E4nGWO\nzFO01syzP4NfqgrUSYiRJXfKtXEP/Dn4fk+fymnRUcwo6WRc7i0osaSfEd2bHDsB\nw2nZ3xBl+Q5JKXpyMfQ4XcCibRa1hU/kVDbuQk1nLOIjHandP8POE5wE4Q3saF/V\nbzvFWtWPlB9EXdPVNOpIQwKBgHkJEsIQ72XdUGBxVewu6pQJ4wDWFhzIWL68sJHp\no2w92BRSCkmcTu7gARV1L/b7DHlXPOUD/6UyE15vCmHvZDfLozrHp3AE2YWzMsgQ\nH4ARTVAP3+U603wytkfh6SFRH5JqEiw30fCxBimVMbleo/UcJyID7LPFLkyT1SoM\njA5JAoGACEhaHTXWFYXv+eTJUXFcwhDZ5sRvQYRCTLGSv746lr+SpZ02bEcRvaTH\nv0K1Hph6OzhcCO27VdimngnzsoXoa2OXicWpdjX3hqhXMvwspe9a1y9u7LPZwqJH\n9Kc7VaZP1iS4EIxEc+qx38HqdeiUBcqTMRam6k3mSwactBKCKDI=\n-----END RSA PRIVATE KEY-----\n", + InstallationId: "9912873", + V3Endpoint: ts.URL, + }, + expectedAccessToken: "v1.b71be873ad96e64a84025ae7bee7694a99cb4ba9", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, _ := resource.GenerateAccessToken(&tt.source, time.Unix(1592691442, 0)) + assert.Equal(t, tt.expectedAccessToken, got) + }) + } +} diff --git a/git.go b/git.go index 53f339d4..b9f1f8ad 100644 --- a/git.go +++ b/git.go @@ -11,6 +11,7 @@ import ( "path/filepath" "strconv" "strings" + "time" ) // Git interface for testing purposes. @@ -34,8 +35,12 @@ func NewGitClient(source *Source, dir string, output io.Writer) (*GitClient, err if source.DisableGitLFS { os.Setenv("GIT_LFS_SKIP_SMUDGE", "true") } + accessToken, err := GenerateAccessToken(source, time.Now()) + if err != nil { + return nil, err + } return &GitClient{ - AccessToken: source.AccessToken, + AccessToken: accessToken, Directory: dir, Output: output, }, nil diff --git a/github.go b/github.go index ab10cbdc..30f0afd5 100644 --- a/github.go +++ b/github.go @@ -11,6 +11,7 @@ import ( "path" "strconv" "strings" + "time" "github.com/google/go-github/v28/github" "github.com/shurcooL/githubv4" @@ -57,8 +58,12 @@ func NewGithubClient(s *Source) (*GithubClient, error) { ctx = context.TODO() } + accessToken, err := GenerateAccessToken(s, time.Now()) + if err != nil { + return nil, err + } client := oauth2.NewClient(ctx, oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: s.AccessToken}, + &oauth2.Token{AccessToken: accessToken}, )) var v3 *github.Client diff --git a/go.mod b/go.mod index ef1f5ca4..032fafd7 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/tools v0.0.0-20200423205358-59e73619c742 // indirect google.golang.org/appengine v1.6.6 // indirect + gopkg.in/square/go-jose.v2 v2.5.1 ) go 1.14 diff --git a/go.sum b/go.sum index 82ec24e7..59661cb9 100644 --- a/go.sum +++ b/go.sum @@ -117,6 +117,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= diff --git a/models.go b/models.go index 9e4e7b1c..126b09a2 100644 --- a/models.go +++ b/models.go @@ -13,6 +13,9 @@ import ( type Source struct { Repository string `json:"repository"` AccessToken string `json:"access_token"` + AppId string `json:"app_id"` + PrivateKey string `json:"private_key"` + InstallationId string `json:"installation_id"` V3Endpoint string `json:"v3_endpoint"` V4Endpoint string `json:"v4_endpoint"` Paths []string `json:"paths"` @@ -31,8 +34,8 @@ type Source struct { // Validate the source configuration. func (s *Source) Validate() error { - if s.AccessToken == "" { - return errors.New("access_token must be set") + if s.AccessToken == "" && (s.AppId == "" || s.PrivateKey == "" || s.InstallationId == "") { + return errors.New("access_token or app_id and private_key and installation_id must be set") } if s.Repository == "" { return errors.New("repository must be set") From 9096a976d72b92a632d396b2fa4c8b9cba99af75 Mon Sep 17 00:00:00 2001 From: Torben Neufeldt Date: Sun, 21 Jun 2020 01:40:10 +0200 Subject: [PATCH 2/7] Add documentation --- README.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d46077ff..026b120e 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,34 @@ Inspired by [the original][original-resource], with some important differences: Make sure to check out [#migrating](#migrating) to learn more. -## Source Configuration +## Configuration + +### Authentication +You can either use a personal access token or let the resource run as a [Github App](https://developer.github.com/apps/). + +#### Personal access token +Please set the `access_token`. +If you want github-pr-resource to work with a private repository. Set `repo:full` permissions on the access token you create on GitHub. If it is a public repository, `repo:status` is enough. + +#### Github App +This is useful when you are part of an organisation and do not want to share your personal access token with everyone else having access to your Secrets Manager. +Please provide `app_id`, `private_key`, `installation_id`. + +We need the following permissions: +- Contents - Read-only => required +- Pull-requests - Read-only => required +- Pull-requests - Read & write => To write/delete comments on pull requests +- Commit statuses - Read & write => To set a commit status + +### Source | Parameter | Required | Example | Description | |-----------------------------|----------|----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `repository` | Yes | `itsdalmo/test-repository` | The repository to target. | -| `access_token` | Yes | | A Github Access Token with repository access (required for setting status on commits). N.B. If you want github-pr-resource to work with a private repository. Set `repo:full` permissions on the access token you create on GitHub. If it is a public repository, `repo:status` is enough. | +| `access_token` | Auth | | A Github Access Token with repository access (required for setting status on commits). | +| `app_id` | Auth | `69592` | The Github App app id you can find on top of the overview page. | +| `private_key` | Auth | `-----BEGIN RSA PRIVATE KEY....` | The private key of your Github App as described [here](https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/). | +| `installation_id` | Auth | `9912873` | On the overview page of your app you can find `Install App` on the left. After installing it you can click on a specific installation and retrieve the numeric installation id from the url. | | `v3_endpoint` | No | `https://api.github.com` | Endpoint to use for the V3 Github API (Restful). | | `v4_endpoint` | No | `https://api.github.com/graphql` | Endpoint to use for the V4 Github API (Graphql). | | `paths` | No | `["terraform/*/*.tf"]` | Only produce new versions if the PR includes changes to files that match one or more glob patterns or prefixes. | From 57972d446429f65c9bfc53f87dd117c0b8d70d2d Mon Sep 17 00:00:00 2001 From: Torben Neufeldt Date: Sun, 2 Aug 2020 21:35:40 +0200 Subject: [PATCH 3/7] Update readme link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 026b120e..f797439e 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ We need the following permissions: | `repository` | Yes | `itsdalmo/test-repository` | The repository to target. | | `access_token` | Auth | | A Github Access Token with repository access (required for setting status on commits). | | `app_id` | Auth | `69592` | The Github App app id you can find on top of the overview page. | -| `private_key` | Auth | `-----BEGIN RSA PRIVATE KEY....` | The private key of your Github App as described [here](https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/). | +| `private_key` | Auth | `-----BEGIN RSA PRIVATE KEY....` | The private key of your Github App as described [here](https://docs.github.com/en/developers/apps/authenticating-with-github-apps). | | `installation_id` | Auth | `9912873` | On the overview page of your app you can find `Install App` on the left. After installing it you can click on a specific installation and retrieve the numeric installation id from the url. | | `v3_endpoint` | No | `https://api.github.com` | Endpoint to use for the V3 Github API (Restful). | | `v4_endpoint` | No | `https://api.github.com/graphql` | Endpoint to use for the V4 Github API (Graphql). | From 4ad5b0069ad77b0d9ec4fa442d421613ffb2be44 Mon Sep 17 00:00:00 2001 From: Torben Neufeldt Date: Sun, 2 Aug 2020 21:58:22 +0200 Subject: [PATCH 4/7] Lookup installationId automatically --- README.md | 5 ++-- authentication.go | 36 +++++++++++++++++------ authentication_test.go | 66 +++++++++++++++++++++++++++++++++++++----- models.go | 5 ++-- 4 files changed, 90 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index f797439e..fbe77eb6 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ If you want github-pr-resource to work with a private repository. Set `repo:full #### Github App This is useful when you are part of an organisation and do not want to share your personal access token with everyone else having access to your Secrets Manager. -Please provide `app_id`, `private_key`, `installation_id`. +Please provide `app_id` and `private_key`. We need the following permissions: - Contents - Read-only => required @@ -42,9 +42,8 @@ We need the following permissions: |-----------------------------|----------|----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `repository` | Yes | `itsdalmo/test-repository` | The repository to target. | | `access_token` | Auth | | A Github Access Token with repository access (required for setting status on commits). | -| `app_id` | Auth | `69592` | The Github App app id you can find on top of the overview page. | +| `app_id` | Auth | `69592` | The Github App app id can be found in the settings of your app. | | `private_key` | Auth | `-----BEGIN RSA PRIVATE KEY....` | The private key of your Github App as described [here](https://docs.github.com/en/developers/apps/authenticating-with-github-apps). | -| `installation_id` | Auth | `9912873` | On the overview page of your app you can find `Install App` on the left. After installing it you can click on a specific installation and retrieve the numeric installation id from the url. | | `v3_endpoint` | No | `https://api.github.com` | Endpoint to use for the V3 Github API (Restful). | | `v4_endpoint` | No | `https://api.github.com/graphql` | Endpoint to use for the V4 Github API (Graphql). | | `paths` | No | `["terraform/*/*.tf"]` | Only produce new versions if the PR includes changes to files that match one or more glob patterns or prefixes. | diff --git a/authentication.go b/authentication.go index 58afc767..64adf942 100644 --- a/authentication.go +++ b/authentication.go @@ -7,10 +7,15 @@ import ( "gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2/jwt" "net/http" + "strconv" "time" ) -type Response struct { +type InstallationResponse struct { + Id int +} + +type TokenResponse struct { Token string } @@ -42,24 +47,37 @@ func GenerateAccessToken(s *Source, now time.Time) (string, error) { } else { endpoint = "https://api.github.com" } - request, err := http.NewRequest("POST", endpoint+"/app/installations/"+s.InstallationId+"/access_tokens", nil) + + installationResponse := callApi("GET", endpoint+"/repos/"+s.Repository+"/installation", signedJwt) + var ir InstallationResponse + err = json.NewDecoder(installationResponse.Body).Decode(&ir) if err != nil { panic(err) } - request.Header.Add("Authorization", "Bearer "+signedJwt) - request.Header.Add("Accept", "application/vnd.github.machine-man-preview+json") - client := &http.Client{} - response, err := client.Do(request) + tokenResponse := callApi("POST", endpoint+"/app/installations/"+strconv.Itoa(ir.Id)+"/access_tokens", signedJwt) + + var tr TokenResponse + err = json.NewDecoder(tokenResponse.Body).Decode(&tr) if err != nil { panic(err) } - var r Response - err = json.NewDecoder(response.Body).Decode(&r) + return tr.Token, nil +} + +func callApi(method string, endpoint string, signedJwt string) *http.Response { + tokenRequest, err := http.NewRequest(method, endpoint, nil) if err != nil { panic(err) } + tokenRequest.Header.Add("Authorization", "Bearer "+signedJwt) + tokenRequest.Header.Add("Accept", "application/vnd.github.machine-man-preview+json") + client := &http.Client{} + response, err := client.Do(tokenRequest) - return r.Token, nil + if err != nil { + panic(err) + } + return response } diff --git a/authentication_test.go b/authentication_test.go index 95c9ecef..cdeb8758 100644 --- a/authentication_test.go +++ b/authentication_test.go @@ -12,7 +12,52 @@ import ( func TestGenerateAccessToken(t *testing.T) { - validResponse := ` + validInstallationResponse := ` +{ + "id": 9912873, + "account": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "avatar_url": "https://github.com/images/error/hubot_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/orgs/github", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "Organization", + "site_admin": false + }, + "repository_selection": "all", + "access_tokens_url": "https://api.github.com/installations/1/access_tokens", + "repositories_url": "https://api.github.com/installation/repositories", + "html_url": "https://github.com/organizations/github/settings/installations/1", + "app_id": 1, + "target_id": 1, + "target_type": "Organization", + "permissions": { + "checks": "write", + "metadata": "read", + "contents": "read" + }, + "events": [ + "push", + "pull_request" + ], + "created_at": "2018-02-09T20:51:14Z", + "updated_at": "2018-02-09T20:51:14Z", + "single_file_name": null +} +` + + validTokenResponse := ` { "token": "v1.b71be873ad96e64a84025ae7bee7694a99cb4ba9", "expires_at": "2020-06-21T00:03:29Z", @@ -27,11 +72,19 @@ func TestGenerateAccessToken(t *testing.T) { ` ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "POST", r.Method) - assert.Equal(t, "/app/installations/9912873/access_tokens", r.RequestURI) - assert.Equal(t, "application/vnd.github.machine-man-preview+json", r.Header.Get("Accept")) - assert.Equal(t, "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTI2OTE5ODIsImlhdCI6MTU5MjY5MTQ0MiwiaXNzIjoiNjk1OTIifQ.H_a6i7TpaGOsaoliH_i7AT5UMwM9LqO21lEFYiZ96_H15cEF6D_kyZrcHyinP2fSC8rX_OQ-DvPsehTNTtfOhgM-nsdgg-gTzdCSlASgc00sGhw_pjBFwHpD6V1NQojc82L8SAR9Bg75g0xVlQ_dAR_Lbtmk252X_AlabRAfdSchK_GVdc3kSEbp28lc87EF7J_lFdRCNuVi1xcLFOXPmMeu4epSBf1ZMtuts28C7iqaI4QJ9keaGFug1wpL-WLDcFbvmB2nJhBYN9tArGM0ZHZ5i4EhFyFjGpBwTyo5P7WY7P3zYtz36gwgntYRtPPivcFQ-wUWuvMpL6vKd-Pp8w", r.Header.Get("Authorization")) - _, _ = fmt.Fprintln(w, validResponse) + if r.RequestURI == "/repos/itsdalmo/test-repository/installation" { + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "application/vnd.github.machine-man-preview+json", r.Header.Get("Accept")) + assert.Equal(t, "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTI2OTE5ODIsImlhdCI6MTU5MjY5MTQ0MiwiaXNzIjoiNjk1OTIifQ.H_a6i7TpaGOsaoliH_i7AT5UMwM9LqO21lEFYiZ96_H15cEF6D_kyZrcHyinP2fSC8rX_OQ-DvPsehTNTtfOhgM-nsdgg-gTzdCSlASgc00sGhw_pjBFwHpD6V1NQojc82L8SAR9Bg75g0xVlQ_dAR_Lbtmk252X_AlabRAfdSchK_GVdc3kSEbp28lc87EF7J_lFdRCNuVi1xcLFOXPmMeu4epSBf1ZMtuts28C7iqaI4QJ9keaGFug1wpL-WLDcFbvmB2nJhBYN9tArGM0ZHZ5i4EhFyFjGpBwTyo5P7WY7P3zYtz36gwgntYRtPPivcFQ-wUWuvMpL6vKd-Pp8w", r.Header.Get("Authorization")) + _, _ = fmt.Fprintln(w, validInstallationResponse) + } + + if r.RequestURI == "/app/installations/9912873/access_tokens" { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "application/vnd.github.machine-man-preview+json", r.Header.Get("Accept")) + assert.Equal(t, "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTI2OTE5ODIsImlhdCI6MTU5MjY5MTQ0MiwiaXNzIjoiNjk1OTIifQ.H_a6i7TpaGOsaoliH_i7AT5UMwM9LqO21lEFYiZ96_H15cEF6D_kyZrcHyinP2fSC8rX_OQ-DvPsehTNTtfOhgM-nsdgg-gTzdCSlASgc00sGhw_pjBFwHpD6V1NQojc82L8SAR9Bg75g0xVlQ_dAR_Lbtmk252X_AlabRAfdSchK_GVdc3kSEbp28lc87EF7J_lFdRCNuVi1xcLFOXPmMeu4epSBf1ZMtuts28C7iqaI4QJ9keaGFug1wpL-WLDcFbvmB2nJhBYN9tArGM0ZHZ5i4EhFyFjGpBwTyo5P7WY7P3zYtz36gwgntYRtPPivcFQ-wUWuvMpL6vKd-Pp8w", r.Header.Get("Authorization")) + _, _ = fmt.Fprintln(w, validTokenResponse) + } })) defer ts.Close() @@ -54,7 +107,6 @@ func TestGenerateAccessToken(t *testing.T) { Repository: "itsdalmo/test-repository", AppId: "69592", PrivateKey: "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAv6oJaa+0BCOT0dITtJK6oPfrE2R0Iynofj/a/vq4rfAw2MJp\nXA5l6WbwmQhevSm8sIYNb2T32qLyRsVXwBo1J8ovIoTXiPdsV41D19tytjctnf+u\n0LncTo2JJR3ik7/Ynu5Id4zjnsn1pYqwLX4EFxgCuEKwdZutPyiY2J7wATfsTtOJ\nsLF8idQijG23i5Obs6AWCZcHOhvdgfYAUOxLv2WRkCG5O1aXYa6nqVn0AgRgKjaJ\nnAENEoG7O9OEWIcUiG30riQouxMHfj0bATCvYoj7a2tvn4CUqk+SBODyh3Fvi0oC\na/NCBYFeK/5uUNyXQHqWy7xEVRef5lC0XbrUVwIDAQABAoIBAFDdqhkIQ/iXFjAp\n5ZyDZ/CwiWNmN8X6UZiq0nhQSolA1SsvY4qunHsMrqiyql4/dNg5xwNf419A7t3D\nN5HavOCr4pU63UFxuyl5dc1mTpDo2PtXvGdec8BE4T9iy40xHXF48eRW8la1uUn+\nKPUYvRsNS2B46sDETSVfuJV1AahRN6aD2WnzQ7wB+S/mqsPXqy+S2zobnU70Wzmt\nhYQzsuIY+BkfOCS565guYvJt66wRGi/NsnybC6z3iRZxZygtKorMKXPjmtdoyCZ3\njkOHhXV5XH8Ldut+1mycg+6c+dTZ9RTrAzo4ouofptm9+ZNlv1aK7HrDDKYQ/x/z\n70hhIfkCgYEA7aNt7db3S6sTEA+yC5DLxxkFIK7K8qxdP6vE087fdHO+ik1LvyI+\ni5Wqj/fT2d/lYR+cbH/zy2JBy5RWfdVJ9HBW/eZ9RqQx8ry7lE5MqbwU6y+d2bCF\nAjffx3yJK1aljuVLdGeu8abYsusUQUhbslZJNg6Ar7z+FUHaChJI0KsCgYEAznk4\nx+5PtvIWj6aXTJiqtofnOtHXXxrq5alzBErvDBHHzP9/ZCucHxJZ4zqHWduj2+9b\nJUgk2BH3+wFMVKpcdTld2iTFWGaFsArTJkE4SBQGx3zcdsoESOnu/DrOTFNu+LiV\nhLzb9CHKM91fIet8MYYxKiyH09+Mi7xBtw8WQwUCgYAH/tCrCOmHHTll9/E4nGWO\nzFO01syzP4NfqgrUSYiRJXfKtXEP/Dn4fk+fymnRUcwo6WRc7i0osaSfEd2bHDsB\nw2nZ3xBl+Q5JKXpyMfQ4XcCibRa1hU/kVDbuQk1nLOIjHandP8POE5wE4Q3saF/V\nbzvFWtWPlB9EXdPVNOpIQwKBgHkJEsIQ72XdUGBxVewu6pQJ4wDWFhzIWL68sJHp\no2w92BRSCkmcTu7gARV1L/b7DHlXPOUD/6UyE15vCmHvZDfLozrHp3AE2YWzMsgQ\nH4ARTVAP3+U603wytkfh6SFRH5JqEiw30fCxBimVMbleo/UcJyID7LPFLkyT1SoM\njA5JAoGACEhaHTXWFYXv+eTJUXFcwhDZ5sRvQYRCTLGSv746lr+SpZ02bEcRvaTH\nv0K1Hph6OzhcCO27VdimngnzsoXoa2OXicWpdjX3hqhXMvwspe9a1y9u7LPZwqJH\n9Kc7VaZP1iS4EIxEc+qx38HqdeiUBcqTMRam6k3mSwactBKCKDI=\n-----END RSA PRIVATE KEY-----\n", - InstallationId: "9912873", V3Endpoint: ts.URL, }, expectedAccessToken: "v1.b71be873ad96e64a84025ae7bee7694a99cb4ba9", diff --git a/models.go b/models.go index 126b09a2..e37adfbf 100644 --- a/models.go +++ b/models.go @@ -15,7 +15,6 @@ type Source struct { AccessToken string `json:"access_token"` AppId string `json:"app_id"` PrivateKey string `json:"private_key"` - InstallationId string `json:"installation_id"` V3Endpoint string `json:"v3_endpoint"` V4Endpoint string `json:"v4_endpoint"` Paths []string `json:"paths"` @@ -34,8 +33,8 @@ type Source struct { // Validate the source configuration. func (s *Source) Validate() error { - if s.AccessToken == "" && (s.AppId == "" || s.PrivateKey == "" || s.InstallationId == "") { - return errors.New("access_token or app_id and private_key and installation_id must be set") + if s.AccessToken == "" && (s.AppId == "" || s.PrivateKey == "") { + return errors.New("access_token or app_id and private_key must be set") } if s.Repository == "" { return errors.New("repository must be set") From b95007f30987716466910c13f1c87166c4d9adf6 Mon Sep 17 00:00:00 2001 From: Torben Neufeldt Date: Sun, 23 Aug 2020 13:15:06 +0200 Subject: [PATCH 5/7] Add better error log for failing http requests --- authentication.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/authentication.go b/authentication.go index 64adf942..f0fc81dd 100644 --- a/authentication.go +++ b/authentication.go @@ -4,9 +4,11 @@ import ( "crypto/x509" "encoding/json" "encoding/pem" + "fmt" "gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2/jwt" "net/http" + "os" "strconv" "time" ) @@ -52,6 +54,8 @@ func GenerateAccessToken(s *Source, now time.Time) (string, error) { var ir InstallationResponse err = json.NewDecoder(installationResponse.Body).Decode(&ir) if err != nil { + _, _ = fmt.Fprintln(os.Stderr, fmt.Sprintf("Error decoding installation response with status %d", installationResponse.StatusCode)) + _, _ = fmt.Fprintln(os.Stderr, installationResponse.Body) panic(err) } @@ -60,6 +64,8 @@ func GenerateAccessToken(s *Source, now time.Time) (string, error) { var tr TokenResponse err = json.NewDecoder(tokenResponse.Body).Decode(&tr) if err != nil { + _, _ = fmt.Fprintln(os.Stderr, fmt.Sprintf("Error decoding token response with status %d", tokenResponse.StatusCode)) + _, _ = fmt.Fprintln(os.Stderr, tokenResponse.Body) panic(err) } From 5dc5163141d3672fd3f3c9c8cad6fa6990f357e3 Mon Sep 17 00:00:00 2001 From: Torben Neufeldt Date: Sun, 23 Aug 2020 13:15:27 +0200 Subject: [PATCH 6/7] Handle trailing slash of GitHub endpoint --- authentication.go | 3 ++- authentication_test.go | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/authentication.go b/authentication.go index f0fc81dd..06fa46ac 100644 --- a/authentication.go +++ b/authentication.go @@ -10,6 +10,7 @@ import ( "net/http" "os" "strconv" + "strings" "time" ) @@ -45,7 +46,7 @@ func GenerateAccessToken(s *Source, now time.Time) (string, error) { var endpoint string if s.V3Endpoint != "" { - endpoint = s.V3Endpoint + endpoint = strings.TrimRight(s.V3Endpoint, "/") } else { endpoint = "https://api.github.com" } diff --git a/authentication_test.go b/authentication_test.go index cdeb8758..dab25738 100644 --- a/authentication_test.go +++ b/authentication_test.go @@ -111,10 +111,21 @@ func TestGenerateAccessToken(t *testing.T) { }, expectedAccessToken: "v1.b71be873ad96e64a84025ae7bee7694a99cb4ba9", }, + { + description: "handle trailing slash", + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AppId: "69592", + PrivateKey: "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAv6oJaa+0BCOT0dITtJK6oPfrE2R0Iynofj/a/vq4rfAw2MJp\nXA5l6WbwmQhevSm8sIYNb2T32qLyRsVXwBo1J8ovIoTXiPdsV41D19tytjctnf+u\n0LncTo2JJR3ik7/Ynu5Id4zjnsn1pYqwLX4EFxgCuEKwdZutPyiY2J7wATfsTtOJ\nsLF8idQijG23i5Obs6AWCZcHOhvdgfYAUOxLv2WRkCG5O1aXYa6nqVn0AgRgKjaJ\nnAENEoG7O9OEWIcUiG30riQouxMHfj0bATCvYoj7a2tvn4CUqk+SBODyh3Fvi0oC\na/NCBYFeK/5uUNyXQHqWy7xEVRef5lC0XbrUVwIDAQABAoIBAFDdqhkIQ/iXFjAp\n5ZyDZ/CwiWNmN8X6UZiq0nhQSolA1SsvY4qunHsMrqiyql4/dNg5xwNf419A7t3D\nN5HavOCr4pU63UFxuyl5dc1mTpDo2PtXvGdec8BE4T9iy40xHXF48eRW8la1uUn+\nKPUYvRsNS2B46sDETSVfuJV1AahRN6aD2WnzQ7wB+S/mqsPXqy+S2zobnU70Wzmt\nhYQzsuIY+BkfOCS565guYvJt66wRGi/NsnybC6z3iRZxZygtKorMKXPjmtdoyCZ3\njkOHhXV5XH8Ldut+1mycg+6c+dTZ9RTrAzo4ouofptm9+ZNlv1aK7HrDDKYQ/x/z\n70hhIfkCgYEA7aNt7db3S6sTEA+yC5DLxxkFIK7K8qxdP6vE087fdHO+ik1LvyI+\ni5Wqj/fT2d/lYR+cbH/zy2JBy5RWfdVJ9HBW/eZ9RqQx8ry7lE5MqbwU6y+d2bCF\nAjffx3yJK1aljuVLdGeu8abYsusUQUhbslZJNg6Ar7z+FUHaChJI0KsCgYEAznk4\nx+5PtvIWj6aXTJiqtofnOtHXXxrq5alzBErvDBHHzP9/ZCucHxJZ4zqHWduj2+9b\nJUgk2BH3+wFMVKpcdTld2iTFWGaFsArTJkE4SBQGx3zcdsoESOnu/DrOTFNu+LiV\nhLzb9CHKM91fIet8MYYxKiyH09+Mi7xBtw8WQwUCgYAH/tCrCOmHHTll9/E4nGWO\nzFO01syzP4NfqgrUSYiRJXfKtXEP/Dn4fk+fymnRUcwo6WRc7i0osaSfEd2bHDsB\nw2nZ3xBl+Q5JKXpyMfQ4XcCibRa1hU/kVDbuQk1nLOIjHandP8POE5wE4Q3saF/V\nbzvFWtWPlB9EXdPVNOpIQwKBgHkJEsIQ72XdUGBxVewu6pQJ4wDWFhzIWL68sJHp\no2w92BRSCkmcTu7gARV1L/b7DHlXPOUD/6UyE15vCmHvZDfLozrHp3AE2YWzMsgQ\nH4ARTVAP3+U603wytkfh6SFRH5JqEiw30fCxBimVMbleo/UcJyID7LPFLkyT1SoM\njA5JAoGACEhaHTXWFYXv+eTJUXFcwhDZ5sRvQYRCTLGSv746lr+SpZ02bEcRvaTH\nv0K1Hph6OzhcCO27VdimngnzsoXoa2OXicWpdjX3hqhXMvwspe9a1y9u7LPZwqJH\n9Kc7VaZP1iS4EIxEc+qx38HqdeiUBcqTMRam6k3mSwactBKCKDI=\n-----END RSA PRIVATE KEY-----\n", + V3Endpoint: ts.URL + "/", + }, + expectedAccessToken: "v1.b71be873ad96e64a84025ae7bee7694a99cb4ba9", + }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - got, _ := resource.GenerateAccessToken(&tt.source, time.Unix(1592691442, 0)) + got, err := resource.GenerateAccessToken(&tt.source, time.Unix(1592691442, 0)) + assert.Nil(t, err) assert.Equal(t, tt.expectedAccessToken, got) }) } From 2bb4f783e1112659a7d85c8d5454224c7ffa28af Mon Sep 17 00:00:00 2001 From: Torben Neufeldt Date: Sun, 23 Aug 2020 13:25:25 +0200 Subject: [PATCH 7/7] Fix formatting --- authentication_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/authentication_test.go b/authentication_test.go index dab25738..8c4a6c44 100644 --- a/authentication_test.go +++ b/authentication_test.go @@ -104,20 +104,20 @@ func TestGenerateAccessToken(t *testing.T) { { description: "create access token", source: resource.Source{ - Repository: "itsdalmo/test-repository", - AppId: "69592", - PrivateKey: "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAv6oJaa+0BCOT0dITtJK6oPfrE2R0Iynofj/a/vq4rfAw2MJp\nXA5l6WbwmQhevSm8sIYNb2T32qLyRsVXwBo1J8ovIoTXiPdsV41D19tytjctnf+u\n0LncTo2JJR3ik7/Ynu5Id4zjnsn1pYqwLX4EFxgCuEKwdZutPyiY2J7wATfsTtOJ\nsLF8idQijG23i5Obs6AWCZcHOhvdgfYAUOxLv2WRkCG5O1aXYa6nqVn0AgRgKjaJ\nnAENEoG7O9OEWIcUiG30riQouxMHfj0bATCvYoj7a2tvn4CUqk+SBODyh3Fvi0oC\na/NCBYFeK/5uUNyXQHqWy7xEVRef5lC0XbrUVwIDAQABAoIBAFDdqhkIQ/iXFjAp\n5ZyDZ/CwiWNmN8X6UZiq0nhQSolA1SsvY4qunHsMrqiyql4/dNg5xwNf419A7t3D\nN5HavOCr4pU63UFxuyl5dc1mTpDo2PtXvGdec8BE4T9iy40xHXF48eRW8la1uUn+\nKPUYvRsNS2B46sDETSVfuJV1AahRN6aD2WnzQ7wB+S/mqsPXqy+S2zobnU70Wzmt\nhYQzsuIY+BkfOCS565guYvJt66wRGi/NsnybC6z3iRZxZygtKorMKXPjmtdoyCZ3\njkOHhXV5XH8Ldut+1mycg+6c+dTZ9RTrAzo4ouofptm9+ZNlv1aK7HrDDKYQ/x/z\n70hhIfkCgYEA7aNt7db3S6sTEA+yC5DLxxkFIK7K8qxdP6vE087fdHO+ik1LvyI+\ni5Wqj/fT2d/lYR+cbH/zy2JBy5RWfdVJ9HBW/eZ9RqQx8ry7lE5MqbwU6y+d2bCF\nAjffx3yJK1aljuVLdGeu8abYsusUQUhbslZJNg6Ar7z+FUHaChJI0KsCgYEAznk4\nx+5PtvIWj6aXTJiqtofnOtHXXxrq5alzBErvDBHHzP9/ZCucHxJZ4zqHWduj2+9b\nJUgk2BH3+wFMVKpcdTld2iTFWGaFsArTJkE4SBQGx3zcdsoESOnu/DrOTFNu+LiV\nhLzb9CHKM91fIet8MYYxKiyH09+Mi7xBtw8WQwUCgYAH/tCrCOmHHTll9/E4nGWO\nzFO01syzP4NfqgrUSYiRJXfKtXEP/Dn4fk+fymnRUcwo6WRc7i0osaSfEd2bHDsB\nw2nZ3xBl+Q5JKXpyMfQ4XcCibRa1hU/kVDbuQk1nLOIjHandP8POE5wE4Q3saF/V\nbzvFWtWPlB9EXdPVNOpIQwKBgHkJEsIQ72XdUGBxVewu6pQJ4wDWFhzIWL68sJHp\no2w92BRSCkmcTu7gARV1L/b7DHlXPOUD/6UyE15vCmHvZDfLozrHp3AE2YWzMsgQ\nH4ARTVAP3+U603wytkfh6SFRH5JqEiw30fCxBimVMbleo/UcJyID7LPFLkyT1SoM\njA5JAoGACEhaHTXWFYXv+eTJUXFcwhDZ5sRvQYRCTLGSv746lr+SpZ02bEcRvaTH\nv0K1Hph6OzhcCO27VdimngnzsoXoa2OXicWpdjX3hqhXMvwspe9a1y9u7LPZwqJH\n9Kc7VaZP1iS4EIxEc+qx38HqdeiUBcqTMRam6k3mSwactBKCKDI=\n-----END RSA PRIVATE KEY-----\n", - V3Endpoint: ts.URL, + Repository: "itsdalmo/test-repository", + AppId: "69592", + PrivateKey: "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAv6oJaa+0BCOT0dITtJK6oPfrE2R0Iynofj/a/vq4rfAw2MJp\nXA5l6WbwmQhevSm8sIYNb2T32qLyRsVXwBo1J8ovIoTXiPdsV41D19tytjctnf+u\n0LncTo2JJR3ik7/Ynu5Id4zjnsn1pYqwLX4EFxgCuEKwdZutPyiY2J7wATfsTtOJ\nsLF8idQijG23i5Obs6AWCZcHOhvdgfYAUOxLv2WRkCG5O1aXYa6nqVn0AgRgKjaJ\nnAENEoG7O9OEWIcUiG30riQouxMHfj0bATCvYoj7a2tvn4CUqk+SBODyh3Fvi0oC\na/NCBYFeK/5uUNyXQHqWy7xEVRef5lC0XbrUVwIDAQABAoIBAFDdqhkIQ/iXFjAp\n5ZyDZ/CwiWNmN8X6UZiq0nhQSolA1SsvY4qunHsMrqiyql4/dNg5xwNf419A7t3D\nN5HavOCr4pU63UFxuyl5dc1mTpDo2PtXvGdec8BE4T9iy40xHXF48eRW8la1uUn+\nKPUYvRsNS2B46sDETSVfuJV1AahRN6aD2WnzQ7wB+S/mqsPXqy+S2zobnU70Wzmt\nhYQzsuIY+BkfOCS565guYvJt66wRGi/NsnybC6z3iRZxZygtKorMKXPjmtdoyCZ3\njkOHhXV5XH8Ldut+1mycg+6c+dTZ9RTrAzo4ouofptm9+ZNlv1aK7HrDDKYQ/x/z\n70hhIfkCgYEA7aNt7db3S6sTEA+yC5DLxxkFIK7K8qxdP6vE087fdHO+ik1LvyI+\ni5Wqj/fT2d/lYR+cbH/zy2JBy5RWfdVJ9HBW/eZ9RqQx8ry7lE5MqbwU6y+d2bCF\nAjffx3yJK1aljuVLdGeu8abYsusUQUhbslZJNg6Ar7z+FUHaChJI0KsCgYEAznk4\nx+5PtvIWj6aXTJiqtofnOtHXXxrq5alzBErvDBHHzP9/ZCucHxJZ4zqHWduj2+9b\nJUgk2BH3+wFMVKpcdTld2iTFWGaFsArTJkE4SBQGx3zcdsoESOnu/DrOTFNu+LiV\nhLzb9CHKM91fIet8MYYxKiyH09+Mi7xBtw8WQwUCgYAH/tCrCOmHHTll9/E4nGWO\nzFO01syzP4NfqgrUSYiRJXfKtXEP/Dn4fk+fymnRUcwo6WRc7i0osaSfEd2bHDsB\nw2nZ3xBl+Q5JKXpyMfQ4XcCibRa1hU/kVDbuQk1nLOIjHandP8POE5wE4Q3saF/V\nbzvFWtWPlB9EXdPVNOpIQwKBgHkJEsIQ72XdUGBxVewu6pQJ4wDWFhzIWL68sJHp\no2w92BRSCkmcTu7gARV1L/b7DHlXPOUD/6UyE15vCmHvZDfLozrHp3AE2YWzMsgQ\nH4ARTVAP3+U603wytkfh6SFRH5JqEiw30fCxBimVMbleo/UcJyID7LPFLkyT1SoM\njA5JAoGACEhaHTXWFYXv+eTJUXFcwhDZ5sRvQYRCTLGSv746lr+SpZ02bEcRvaTH\nv0K1Hph6OzhcCO27VdimngnzsoXoa2OXicWpdjX3hqhXMvwspe9a1y9u7LPZwqJH\n9Kc7VaZP1iS4EIxEc+qx38HqdeiUBcqTMRam6k3mSwactBKCKDI=\n-----END RSA PRIVATE KEY-----\n", + V3Endpoint: ts.URL, }, expectedAccessToken: "v1.b71be873ad96e64a84025ae7bee7694a99cb4ba9", }, { description: "handle trailing slash", source: resource.Source{ - Repository: "itsdalmo/test-repository", - AppId: "69592", - PrivateKey: "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAv6oJaa+0BCOT0dITtJK6oPfrE2R0Iynofj/a/vq4rfAw2MJp\nXA5l6WbwmQhevSm8sIYNb2T32qLyRsVXwBo1J8ovIoTXiPdsV41D19tytjctnf+u\n0LncTo2JJR3ik7/Ynu5Id4zjnsn1pYqwLX4EFxgCuEKwdZutPyiY2J7wATfsTtOJ\nsLF8idQijG23i5Obs6AWCZcHOhvdgfYAUOxLv2WRkCG5O1aXYa6nqVn0AgRgKjaJ\nnAENEoG7O9OEWIcUiG30riQouxMHfj0bATCvYoj7a2tvn4CUqk+SBODyh3Fvi0oC\na/NCBYFeK/5uUNyXQHqWy7xEVRef5lC0XbrUVwIDAQABAoIBAFDdqhkIQ/iXFjAp\n5ZyDZ/CwiWNmN8X6UZiq0nhQSolA1SsvY4qunHsMrqiyql4/dNg5xwNf419A7t3D\nN5HavOCr4pU63UFxuyl5dc1mTpDo2PtXvGdec8BE4T9iy40xHXF48eRW8la1uUn+\nKPUYvRsNS2B46sDETSVfuJV1AahRN6aD2WnzQ7wB+S/mqsPXqy+S2zobnU70Wzmt\nhYQzsuIY+BkfOCS565guYvJt66wRGi/NsnybC6z3iRZxZygtKorMKXPjmtdoyCZ3\njkOHhXV5XH8Ldut+1mycg+6c+dTZ9RTrAzo4ouofptm9+ZNlv1aK7HrDDKYQ/x/z\n70hhIfkCgYEA7aNt7db3S6sTEA+yC5DLxxkFIK7K8qxdP6vE087fdHO+ik1LvyI+\ni5Wqj/fT2d/lYR+cbH/zy2JBy5RWfdVJ9HBW/eZ9RqQx8ry7lE5MqbwU6y+d2bCF\nAjffx3yJK1aljuVLdGeu8abYsusUQUhbslZJNg6Ar7z+FUHaChJI0KsCgYEAznk4\nx+5PtvIWj6aXTJiqtofnOtHXXxrq5alzBErvDBHHzP9/ZCucHxJZ4zqHWduj2+9b\nJUgk2BH3+wFMVKpcdTld2iTFWGaFsArTJkE4SBQGx3zcdsoESOnu/DrOTFNu+LiV\nhLzb9CHKM91fIet8MYYxKiyH09+Mi7xBtw8WQwUCgYAH/tCrCOmHHTll9/E4nGWO\nzFO01syzP4NfqgrUSYiRJXfKtXEP/Dn4fk+fymnRUcwo6WRc7i0osaSfEd2bHDsB\nw2nZ3xBl+Q5JKXpyMfQ4XcCibRa1hU/kVDbuQk1nLOIjHandP8POE5wE4Q3saF/V\nbzvFWtWPlB9EXdPVNOpIQwKBgHkJEsIQ72XdUGBxVewu6pQJ4wDWFhzIWL68sJHp\no2w92BRSCkmcTu7gARV1L/b7DHlXPOUD/6UyE15vCmHvZDfLozrHp3AE2YWzMsgQ\nH4ARTVAP3+U603wytkfh6SFRH5JqEiw30fCxBimVMbleo/UcJyID7LPFLkyT1SoM\njA5JAoGACEhaHTXWFYXv+eTJUXFcwhDZ5sRvQYRCTLGSv746lr+SpZ02bEcRvaTH\nv0K1Hph6OzhcCO27VdimngnzsoXoa2OXicWpdjX3hqhXMvwspe9a1y9u7LPZwqJH\n9Kc7VaZP1iS4EIxEc+qx38HqdeiUBcqTMRam6k3mSwactBKCKDI=\n-----END RSA PRIVATE KEY-----\n", - V3Endpoint: ts.URL + "/", + Repository: "itsdalmo/test-repository", + AppId: "69592", + PrivateKey: "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAv6oJaa+0BCOT0dITtJK6oPfrE2R0Iynofj/a/vq4rfAw2MJp\nXA5l6WbwmQhevSm8sIYNb2T32qLyRsVXwBo1J8ovIoTXiPdsV41D19tytjctnf+u\n0LncTo2JJR3ik7/Ynu5Id4zjnsn1pYqwLX4EFxgCuEKwdZutPyiY2J7wATfsTtOJ\nsLF8idQijG23i5Obs6AWCZcHOhvdgfYAUOxLv2WRkCG5O1aXYa6nqVn0AgRgKjaJ\nnAENEoG7O9OEWIcUiG30riQouxMHfj0bATCvYoj7a2tvn4CUqk+SBODyh3Fvi0oC\na/NCBYFeK/5uUNyXQHqWy7xEVRef5lC0XbrUVwIDAQABAoIBAFDdqhkIQ/iXFjAp\n5ZyDZ/CwiWNmN8X6UZiq0nhQSolA1SsvY4qunHsMrqiyql4/dNg5xwNf419A7t3D\nN5HavOCr4pU63UFxuyl5dc1mTpDo2PtXvGdec8BE4T9iy40xHXF48eRW8la1uUn+\nKPUYvRsNS2B46sDETSVfuJV1AahRN6aD2WnzQ7wB+S/mqsPXqy+S2zobnU70Wzmt\nhYQzsuIY+BkfOCS565guYvJt66wRGi/NsnybC6z3iRZxZygtKorMKXPjmtdoyCZ3\njkOHhXV5XH8Ldut+1mycg+6c+dTZ9RTrAzo4ouofptm9+ZNlv1aK7HrDDKYQ/x/z\n70hhIfkCgYEA7aNt7db3S6sTEA+yC5DLxxkFIK7K8qxdP6vE087fdHO+ik1LvyI+\ni5Wqj/fT2d/lYR+cbH/zy2JBy5RWfdVJ9HBW/eZ9RqQx8ry7lE5MqbwU6y+d2bCF\nAjffx3yJK1aljuVLdGeu8abYsusUQUhbslZJNg6Ar7z+FUHaChJI0KsCgYEAznk4\nx+5PtvIWj6aXTJiqtofnOtHXXxrq5alzBErvDBHHzP9/ZCucHxJZ4zqHWduj2+9b\nJUgk2BH3+wFMVKpcdTld2iTFWGaFsArTJkE4SBQGx3zcdsoESOnu/DrOTFNu+LiV\nhLzb9CHKM91fIet8MYYxKiyH09+Mi7xBtw8WQwUCgYAH/tCrCOmHHTll9/E4nGWO\nzFO01syzP4NfqgrUSYiRJXfKtXEP/Dn4fk+fymnRUcwo6WRc7i0osaSfEd2bHDsB\nw2nZ3xBl+Q5JKXpyMfQ4XcCibRa1hU/kVDbuQk1nLOIjHandP8POE5wE4Q3saF/V\nbzvFWtWPlB9EXdPVNOpIQwKBgHkJEsIQ72XdUGBxVewu6pQJ4wDWFhzIWL68sJHp\no2w92BRSCkmcTu7gARV1L/b7DHlXPOUD/6UyE15vCmHvZDfLozrHp3AE2YWzMsgQ\nH4ARTVAP3+U603wytkfh6SFRH5JqEiw30fCxBimVMbleo/UcJyID7LPFLkyT1SoM\njA5JAoGACEhaHTXWFYXv+eTJUXFcwhDZ5sRvQYRCTLGSv746lr+SpZ02bEcRvaTH\nv0K1Hph6OzhcCO27VdimngnzsoXoa2OXicWpdjX3hqhXMvwspe9a1y9u7LPZwqJH\n9Kc7VaZP1iS4EIxEc+qx38HqdeiUBcqTMRam6k3mSwactBKCKDI=\n-----END RSA PRIVATE KEY-----\n", + V3Endpoint: ts.URL + "/", }, expectedAccessToken: "v1.b71be873ad96e64a84025ae7bee7694a99cb4ba9", },