-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: web driver use token sources for authorization (#548)
Currently, only support github app token source using a rsa private key.
- Loading branch information
1 parent
e20add8
commit 545b736
Showing
6 changed files
with
191 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBbURjeVFFZUQrTWRaSFo2Mi9pdXNUUUpHNDR0dlZvdXM5VmgvWkdkY094QTdQaE1PCmJXUXY3Y056aDRhc1lBRkNnUGc4UmtGVTdGQVg3dlBBeG91K1BiR0NGUi9nN1VMSER2amhucGtMRXpRZVp0TnMKc2lUNVRwT0ZFT05zQ3BURlJVYmpmWFBjek40cDQ4TUF6MldMSVIyeDBCWldzRktCb3BnUWxZMTJyMnprR2xHeQo1Z21DczIvdEp6cWdta3d5QUNGKzBRUklXZ1RueGt3ZzFoSjRSR0x1NFd5aFB0TzNnZWxHQUlsMDloV0pFUnY1Cnh2SDBKVFFUQ2ZBcmJmWmZiMHUwRkJod1JrU05KaU5xTlliazNkUVRkSmcwbG9nSlpEY0VMeThhcTIvY0x4VUgKb3F2c0N4UnZMam92dTNkYWUwOGMxTThSM1BGQ2RHaHhCVmVqMXdJREFRQUJBb0lCQUNzT1llNkF6RG5RMmNwaApITTRrdUdaSUlKazQxZE9iU3Q5VG15VmhmMXROcWhSUys1L0IyVFRlTm8yOWNJRHZta28wN1lmSjd5V3hPalBqClMwSmVRUC9lZURkVmZ5QmQ1VVM4N2NVWThXTUxPUlpJODlRb1ZVVCt3WU1YY1haRXd0Qm56dTJybW1kdzZGUisKMG5uWDlWVDJ1MWRyR2paaUFEMW4yamtUZk9EOThwRDBuRGI2VGEvVzV4RURYRTBSdFRxK1ROZ1ZBbGpxOVdqSwo2a1FzdHlVZGJPbXBJYXlibEszK2tQRnFNM25tcVBnaE00UVcyRGg3OHNOUCtZell2VlhDVWVYNTYxVmVSWkt2CmI2Y05jYmF6N3RkajBObW56amJEcjE5KzUvTGZFSDFDV2dnSzg3VmlHaW5jMS95Vm56L2VoeFdHbjZ2N2dzZ3MKTlMyVnRVRUNnWUVBeDVOdGlGb0gwRVhtdmo5aGJQd3MzUWlLMW1hamR3b0JzcTdjQ2t0OWY2WDBlcHdoNlBCNQpMMkJjWS9uRE9DNCtvWENYWUlQOGltRmswaEhFc1AwRWRaQVFIa3dQNHRLeUlQRXByaTdBNUdWZURtWXluSUtNCm9wR2poVXhiVFozNmpzcWxBRnJ5YXFtdHFsdTdmSVJYWDdraE1POHNCSjFZd3VLMFhKKzBsd2NDZ1lFQXcwQUIKTzVmdVRKOWFNTjRsOXRLTEE1aHludS9jMGgxUVVLR09Rc3lwRmsvRHk5enJWVkxJYi9ob2xDUXNPTTFWY1ZvUApSejQ3RjJvWjFkaUNJbmFvZU9kVWNtVzlkMWFwRHFYNmNxbnVWN0JzQUJnY0ViRUt1MkpIWjArUExQTFk0Si9jCnFkNFJqdHFZa25JZVZ2TEYvOUhldVl3RWVZL0IxVlNSck9sbENMRUNnWUJxY3d4ZFNnZ1k0dS9zVWNvWlkzaGEKZlEvd3c5WTB6RFdUcFFqZ3hOc3Zsc2tNRFBOWlY4cUxwbzRoRlRzM1lCTXY4T29OSk5reXhqZ01oRVd4VVlOcgpZV2YzZ1FLSUxYR3RlSFNPMzRrclNaWWRnQTFHeGF0Vm12RHBUSXoybldqamVOc0JrWUR6dTRWUjlKUFFHcGF3CkRBTFVJdjRMaUJHc0FWZktmN1RIU1FLQmdCbU1BMTFIeU05UHZsNU1nczBqeVRxa05NTWxBVkNnczBTSmp2S2cKa3JNdnBwL0MvU3ZCMUNZS2E2eU9leGJIanhsd3ZqVUZLSGdzMHNxUE5KL0x4TWxsQTBDZ25VVERHd1dtby9saQowS082bXJiOGNKZkVBWEo1TG55UEJWM05QS0ZQYVhEMGRIbXJrbkQrNjRkVzVwOU5WNFlSa3ZoUTNmekt2dkRQCjdQOVJBb0dBT0tDK1VYbWQ3UG1sNjZZNzd6Y2J4U0ZySmdMUnVpNjNhSUMvTjQ2dEJCeWpyb0Y1R210MFZCOG4KVEd0T1hBNGJqWENhT3NQZldjWlptTko5Z3AvYkFzeUlabXQzRkRGMlI0MHJORm9kWWdYdGxyRmdBUFNvaEpmSgpRNngzRU1uWmF3V0lJcXNIUmVQU3JHYlFFcElTb0pSS0FJU0NKUDFybXh3NkJpRzlFZEU9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// Copyright 2023 PayPal Inc. | ||
|
||
// This Source Code Form is subject to the terms of the MIT License. | ||
// If a copy of the MIT License was not distributed with this file, | ||
// you can obtain one at https://mit-license.org/. | ||
|
||
// Package web enables Honeydipper to make outbound web requests. | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"crypto/rsa" | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/golang-jwt/jwt/v5" | ||
"github.com/honeydipper/honeydipper/pkg/dipper" | ||
) | ||
|
||
const globalGitHubURL = "https://api.github.com" | ||
|
||
func getToken(source string) string { | ||
s := dipper.MustGetMapData(driver.Options, "token_sources."+source).(map[string]interface{}) | ||
switch s["type"].(string) { | ||
case "github": | ||
|
||
return getGitHubToken(s) | ||
default: | ||
log.Panicf("[%s] unknown token source type: %+v", driver.Service, s["type"]) | ||
} | ||
|
||
return "" | ||
} | ||
|
||
func getGitHubToken(s map[string]interface{}) string { | ||
saved, ok := s["_saved"] | ||
if ok { | ||
exp := dipper.MustGetMapData(s, "_expiresAt").(time.Time) | ||
//nolint: gomnd | ||
if time.Now().Add(2 * time.Second).Before(exp) { | ||
return saved.(string) | ||
} | ||
} | ||
|
||
//nolint: gomnd | ||
expiresAt := time.Now().Add(time.Minute * 15) | ||
claims := &jwt.RegisteredClaims{ | ||
IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Minute * 1)), | ||
ExpiresAt: jwt.NewNumericDate(expiresAt), | ||
Issuer: s["app_id"].(string), | ||
} | ||
jwtToken := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) | ||
|
||
var pk *rsa.PrivateKey | ||
if b, ok := s["_parsed_key"]; ok { | ||
pk = b.(*rsa.PrivateKey) | ||
} else { | ||
b := dipper.MustGetMapDataStr(s, "key") | ||
pk = dipper.Must(jwt.ParseRSAPrivateKeyFromPEM([]byte(b))).(*rsa.PrivateKey) | ||
s["_parsed_key"] = pk | ||
} | ||
jwtTokenStr := dipper.Must(jwtToken.SignedString(pk)).(string) | ||
|
||
header := http.Header{} | ||
header.Set("accept", "application/vnd.github+json") | ||
header.Set("authorization", "Bearer "+jwtTokenStr) | ||
|
||
permissions := dipper.MustGetMapData(s, "permissions").(map[string]interface{}) | ||
contentBytes := dipper.Must(json.Marshal(map[string]interface{}{ | ||
"permissions": permissions, | ||
})).([]byte) | ||
buf := bytes.NewBuffer(contentBytes) | ||
|
||
instID := dipper.MustGetMapDataStr(s, "installation_id") | ||
|
||
u, ok := s["github_url"] | ||
if !ok { | ||
u = globalGitHubURL | ||
} | ||
req := dipper.Must(http.NewRequest("POST", u.(string)+"/app/installations/"+instID+"/access_token", buf)).(*http.Request) | ||
client := http.Client{} | ||
//nolint: bodyClose | ||
resp := dipper.Must(client.Do(req)).(*http.Response) | ||
defer resp.Body.Close() | ||
if resp.StatusCode != http.StatusOK { | ||
log.Panicf("[%s] failed to fetch github access token with status code %+v", driver.Service, resp.StatusCode) | ||
} | ||
|
||
bodyObj := map[string]interface{}{} | ||
dipper.Must(json.Unmarshal(dipper.Must(io.ReadAll(resp.Body)).([]byte), &bodyObj)) | ||
|
||
token := dipper.MustGetMapDataStr(bodyObj, "token") | ||
s["_saved"] = token | ||
s["_expiresAt"] = expiresAt | ||
|
||
return token | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Copyright 2023 PayPal Inc. | ||
|
||
// This Source Code Form is subject to the terms of the MIT License. | ||
// If a copy of the MIT License was not distributed with this file, | ||
// you can obtain one at https://mit-license.org/. | ||
|
||
//go:build !integration | ||
// +build !integration | ||
|
||
package main | ||
|
||
import ( | ||
"encoding/base64" | ||
"io/ioutil" | ||
"testing" | ||
"time" | ||
|
||
"github.com/honeydipper/honeydipper/pkg/dipper" | ||
"github.com/stretchr/testify/assert" | ||
"gopkg.in/h2non/gock.v1" | ||
) | ||
|
||
func TestGetToken(t *testing.T) { | ||
defer gock.Off() | ||
|
||
gock.New("https://api.github.com"). | ||
Post("/app/installations/123/access_token"). | ||
Reply(200). | ||
JSON(map[string]string{"token": "foobar"}) | ||
|
||
keyb64 := dipper.Must(ioutil.ReadFile("test_fixtures/testkey")).([]byte) | ||
keybytes := dipper.Must(base64.StdEncoding.DecodeString(string(keyb64))).([]byte) | ||
|
||
githubSource := map[string]interface{}{ | ||
"type": "github", | ||
"app_id": "345", | ||
"installation_id": "123", | ||
"key": string(keybytes), | ||
"permissions": map[string]interface{}{ | ||
"content": "write", | ||
}, | ||
} | ||
|
||
driver.Options = map[string]interface{}{ | ||
"token_sources": map[string]interface{}{ | ||
"test1": githubSource, | ||
}, | ||
} | ||
|
||
var token string | ||
assert.NotPanicsf(t, func() { token = getToken("test1") }, "should not panic when getting token") | ||
assert.Equalf(t, "foobar", token, "should get the test token") | ||
assert.Containsf(t, githubSource, "_saved", "should save the token for future use") | ||
assert.Containsf(t, githubSource, "_expiresAt", "should save the expiresAt") | ||
assert.Containsf(t, githubSource, "_parsed_key", "should save the expiresAt") | ||
|
||
githubSource["_saved"] = "newtoken" | ||
assert.NotPanicsf(t, func() { token = getToken("test1") }, "should not panic when getting token from cache") | ||
assert.Equalf(t, "newtoken", token, "should get the saved token") | ||
|
||
gock.New("https://api.github.com"). | ||
Post("/app/installations/123/access_token"). | ||
Reply(200). | ||
JSON(map[string]string{"token": "foobar2"}) | ||
|
||
githubSource["_expiresAt"] = time.Now().Add(-time.Minute * 15) | ||
assert.NotPanicsf(t, func() { token = getToken("test1") }, "should not panic when refreshing token") | ||
assert.Equalf(t, "foobar2", token, "should get a new token if expired") | ||
|
||
gock.New("https://api.github.com"). | ||
Post("/app/installations/123/access_token"). | ||
Reply(200). | ||
JSON(map[string]string{"token": "foobar3"}) | ||
|
||
githubSource["_expiresAt"] = time.Now().Add(-time.Minute * 15) | ||
githubSource["key"] = "" | ||
assert.NotPanicsf(t, func() { token = getToken("test1") }, "should not panic when refreshing token with _parsed_key") | ||
assert.Equalf(t, "foobar3", token, "should get a new token using _parsed_key") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.