forked from bradleyfalzon/ghinstallation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
appsTransport.go
89 lines (79 loc) · 3.21 KB
/
appsTransport.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package ghinstallation
import (
"crypto/rsa"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"time"
jwt "github.com/golang-jwt/jwt/v4"
)
// AppsTransport provides a http.RoundTripper by wrapping an existing
// http.RoundTripper and provides GitHub Apps authentication as a
// GitHub App.
//
// Client can also be overwritten, and is useful to change to one which
// provides retry logic if you do experience retryable errors.
//
// See https://developer.github.com/apps/building-integrations/setting-up-and-registering-github-apps/about-authentication-options-for-github-apps/
type AppsTransport struct {
BaseURL string // BaseURL is the scheme and host for GitHub API, defaults to https://api.github.com
Client Client // Client to use to refresh tokens, defaults to http.Client with provided transport
tr http.RoundTripper // tr is the underlying roundtripper being wrapped
key *rsa.PrivateKey // key is the GitHub App's private key
appID int64 // appID is the GitHub App's ID
}
// NewAppsTransportKeyFromFile returns a AppsTransport using a private key from file.
func NewAppsTransportKeyFromFile(tr http.RoundTripper, appID int64, privateKeyFile string) (*AppsTransport, error) {
privateKey, err := ioutil.ReadFile(privateKeyFile)
if err != nil {
return nil, fmt.Errorf("could not read private key: %s", err)
}
return NewAppsTransport(tr, appID, privateKey)
}
// NewAppsTransport returns a AppsTransport using private key. The key is parsed
// and if any errors occur the error is non-nil.
//
// The provided tr http.RoundTripper should be shared between multiple
// installations to ensure reuse of underlying TCP connections.
//
// The returned Transport's RoundTrip method is safe to be used concurrently.
func NewAppsTransport(tr http.RoundTripper, appID int64, privateKey []byte) (*AppsTransport, error) {
key, err := jwt.ParseRSAPrivateKeyFromPEM(privateKey)
if err != nil {
return nil, fmt.Errorf("could not parse private key: %s", err)
}
return NewAppsTransportFromPrivateKey(tr, appID, key), nil
}
// NewAppsTransportFromPrivateKey returns an AppsTransport using a crypto/rsa.(*PrivateKey).
func NewAppsTransportFromPrivateKey(tr http.RoundTripper, appID int64, key *rsa.PrivateKey) *AppsTransport {
return &AppsTransport{
BaseURL: apiBaseURL,
Client: &http.Client{Transport: tr},
tr: tr,
key: key,
appID: appID,
}
}
// RoundTrip implements http.RoundTripper interface.
func (t *AppsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// GitHub rejects expiry and issue timestamps that are not an integer,
// while the jwt-go library serializes to fractional timestamps.
// Truncate them before passing to jwt-go.
iss := time.Now().Add(-30 * time.Second).Truncate(time.Second)
exp := iss.Add(2 * time.Minute)
claims := &jwt.StandardClaims{
IssuedAt: iss.Unix(),
ExpiresAt: exp.Unix(),
Issuer: strconv.FormatInt(t.appID, 10),
}
bearer := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
ss, err := bearer.SignedString(t.key)
if err != nil {
return nil, fmt.Errorf("could not sign jwt: %s", err)
}
req.Header.Set("Authorization", "Bearer "+ss)
req.Header.Add("Accept", acceptHeader)
resp, err := t.tr.RoundTrip(req)
return resp, err
}