Skip to content

Commit

Permalink
Lookup installationId automatically
Browse files Browse the repository at this point in the history
  • Loading branch information
tenjaa committed Jul 19, 2021
1 parent 57972d4 commit 4ad5b00
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 22 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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. |
Expand Down
36 changes: 27 additions & 9 deletions authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
66 changes: 59 additions & 7 deletions authentication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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()

Expand All @@ -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",
Expand Down
5 changes: 2 additions & 3 deletions models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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")
Expand Down

0 comments on commit 4ad5b00

Please sign in to comment.