Skip to content

Commit f57151c

Browse files
authored
fix: specific functions for github access tokens (#227)
Create specific function for calling the GitHub API for generating installation access tokens.
1 parent d436434 commit f57151c

File tree

2 files changed

+62
-5
lines changed

2 files changed

+62
-5
lines changed

githubapp/githubapp.go

+35-4
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,18 @@ func NewConfig(appID, installationID string, privateKey *rsa.PrivateKey, opts ..
115115
// requested permissions / scopes that are requested when generating a
116116
// new installation access token.
117117
type TokenRequest struct {
118-
Repositories *[]string `json:"repositories,omitempty"`
118+
Repositories []string `json:"repositories"`
119119
Permissions map[string]string `json:"permissions"`
120120
}
121121

122+
// TokenRequestAllRepos is a struct that contains the requested permissions/scopes
123+
// that are requested when generating a new installation access token.
124+
// This struct intentionally omits the repository properties to generate a token
125+
// for all repositories granted to this GitHub app installation.
126+
type TokenRequestAllRepos struct {
127+
Permissions map[string]string `json:"permissions"`
128+
}
129+
122130
// GitHubApp is an object that can be used to generate application level JWTs
123131
// or to request an OIDC token on behalf of an installation.
124132
type GitHubApp struct {
@@ -203,16 +211,39 @@ func (g *GitHubApp) generateAppJWT() ([]byte, error) {
203211
// access token for this application installation with the requested
204212
// permissions and repositories.
205213
func (g *GitHubApp) AccessToken(ctx context.Context, request *TokenRequest) (string, error) {
206-
appJWT, err := g.AppToken()
214+
if request.Repositories == nil {
215+
return "", fmt.Errorf("requested repositories cannot be nil, did you mean to use AccessTokenAllRepos to request all repos?")
216+
}
217+
218+
requestJSON, err := json.Marshal(request)
207219
if err != nil {
208-
return "", fmt.Errorf("error generating app jwt: %w", err)
220+
return "", fmt.Errorf("error marshalling request data: %w", err)
209221
}
210-
requestURL := fmt.Sprintf(g.config.accessTokenURLPattern, g.config.InstallationID)
222+
223+
return g.githubAccessToken(ctx, requestJSON)
224+
}
225+
226+
// AccessTokenAllRepos calls the GitHub API to generate a new
227+
// access token for this application installation with the requested
228+
// permissions and all granted repositories.
229+
func (g *GitHubApp) AccessTokenAllRepos(ctx context.Context, request *TokenRequestAllRepos) (string, error) {
211230
requestJSON, err := json.Marshal(request)
212231
if err != nil {
213232
return "", fmt.Errorf("error marshalling request data: %w", err)
214233
}
215234

235+
return g.githubAccessToken(ctx, requestJSON)
236+
}
237+
238+
// githubAccessToken calls the GitHub API to generate a new
239+
// access token with provided JSON payload bytes.
240+
func (g *GitHubApp) githubAccessToken(ctx context.Context, requestJSON []byte) (string, error) {
241+
appJWT, err := g.AppToken()
242+
if err != nil {
243+
return "", fmt.Errorf("error generating app jwt: %w", err)
244+
}
245+
requestURL := fmt.Sprintf(g.config.accessTokenURLPattern, g.config.InstallationID)
246+
216247
requestReader := bytes.NewReader(requestJSON)
217248
req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL, requestReader)
218249
if err != nil {

githubapp/githubapp_test.go

+27-1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ func TestGitHubApp_AccessToken(t *testing.T) {
146146
appID string
147147
installID string
148148
options []ConfigOption
149+
request *TokenRequest
149150
want string
150151
expErr string
151152
handlerFunc http.HandlerFunc
@@ -155,6 +156,7 @@ func TestGitHubApp_AccessToken(t *testing.T) {
155156
appID: "test-app-id",
156157
installID: "test-install-id",
157158
options: []ConfigOption{},
159+
request: &TokenRequest{Repositories: []string{"test"}, Permissions: map[string]string{"test": "test"}},
158160
want: `{"token":"this-is-the-token-from-github"}`,
159161
expErr: "",
160162
handlerFunc: nil,
@@ -164,6 +166,7 @@ func TestGitHubApp_AccessToken(t *testing.T) {
164166
appID: "test-app-id",
165167
installID: "test-install-id",
166168
options: []ConfigOption{},
169+
request: &TokenRequest{Repositories: []string{"test"}, Permissions: map[string]string{"test": "test"}},
167170
expErr: "failed to retrieve token from GitHub - Status: 500 Internal Server Error - Body: ",
168171
handlerFunc: func(w http.ResponseWriter, r *http.Request) {
169172
w.WriteHeader(500)
@@ -174,6 +177,7 @@ func TestGitHubApp_AccessToken(t *testing.T) {
174177
appID: "test-app-id",
175178
installID: "test-install-id",
176179
options: []ConfigOption{},
180+
request: &TokenRequest{Repositories: []string{"test"}, Permissions: map[string]string{"test": "test"}},
177181
expErr: "invalid access token from GitHub - Body: not json",
178182
handlerFunc: func(w http.ResponseWriter, r *http.Request) {
179183
w.WriteHeader(201)
@@ -185,11 +189,33 @@ func TestGitHubApp_AccessToken(t *testing.T) {
185189
appID: "test-app-id",
186190
installID: "test-install-id",
187191
options: []ConfigOption{},
192+
request: &TokenRequest{Repositories: []string{"test"}, Permissions: map[string]string{"test": "test"}},
188193
expErr: "invalid access token from GitHub - Body:",
189194
handlerFunc: func(w http.ResponseWriter, r *http.Request) {
190195
w.WriteHeader(201)
191196
},
192197
},
198+
{
199+
name: "allow_empty_repositories",
200+
appID: "test-app-id",
201+
installID: "test-install-id",
202+
options: []ConfigOption{},
203+
request: &TokenRequest{Repositories: []string{}, Permissions: map[string]string{"test": "test"}},
204+
want: `{"token":"this-is-the-token-from-github"}`,
205+
expErr: "",
206+
handlerFunc: nil,
207+
},
208+
{
209+
name: "missing_repositories",
210+
appID: "test-app-id",
211+
installID: "test-install-id",
212+
options: []ConfigOption{},
213+
request: &TokenRequest{Permissions: map[string]string{"test": "test"}},
214+
expErr: "requested repositories cannot be nil, did you mean to use AccessTokenAllRepos to request all repos?",
215+
handlerFunc: func(w http.ResponseWriter, r *http.Request) {
216+
w.WriteHeader(201)
217+
},
218+
},
193219
}
194220

195221
for _, tc := range cases {
@@ -221,7 +247,7 @@ func TestGitHubApp_AccessToken(t *testing.T) {
221247
tc.options = append(tc.options, WithAccessTokenURLPattern(fakeGitHub.URL+"/%s/access_tokens"))
222248

223249
app := New(NewConfig(tc.appID, tc.installID, rsaPrivateKey, tc.options...))
224-
got, err := app.AccessToken(context.Background(), &TokenRequest{})
250+
got, err := app.AccessToken(context.Background(), tc.request)
225251
if diff := testutil.DiffErrString(err, tc.expErr); diff != "" {
226252
t.Errorf(diff)
227253
}

0 commit comments

Comments
 (0)