forked from netlify/git-gateway
-
Notifications
You must be signed in to change notification settings - Fork 0
/
github.go
123 lines (105 loc) · 3.2 KB
/
github.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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package api
import (
"errors"
"net/http"
"net/http/httputil"
"net/url"
"regexp"
)
// GitHubGateway acts as a proxy to GitHub
type GitHubGateway struct {
proxy *httputil.ReverseProxy
}
var pathRegexp = regexp.MustCompile("^/github/?")
var allowedRegexp = regexp.MustCompile("^/github/((git|contents|pulls|branches|merges|statuses|compare|commits)/?|(issues/(\\d+)/labels))")
func NewGitHubGateway() *GitHubGateway {
return &GitHubGateway{
proxy: &httputil.ReverseProxy{
Director: director,
Transport: &GitHubTransport{},
ErrorHandler: proxyErrorHandler,
},
}
}
func director(r *http.Request) {
ctx := r.Context()
target := getProxyTarget(ctx)
accessToken := getAccessToken(ctx)
targetQuery := target.RawQuery
r.Host = target.Host
r.URL.Scheme = target.Scheme
r.URL.Host = target.Host
r.URL.Path = singleJoiningSlash(target.Path, pathRegexp.ReplaceAllString(r.URL.Path, "/"))
if targetQuery == "" || r.URL.RawQuery == "" {
r.URL.RawQuery = targetQuery + r.URL.RawQuery
} else {
r.URL.RawQuery = targetQuery + "&" + r.URL.RawQuery
}
if _, ok := r.Header["User-Agent"]; !ok {
// explicitly disable User-Agent so it's not set to default value
r.Header.Set("User-Agent", "")
}
if r.Method != http.MethodOptions {
r.Header.Set("Authorization", "Bearer "+accessToken)
}
log := getLogEntry(r)
log.Infof("Proxying to GitHub: %v", r.URL.String())
}
func (gh *GitHubGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
config := getConfig(ctx)
if config == nil || config.GitHub.AccessToken == "" {
handleError(notFoundError("No GitHub Settings Configured"), w, r)
return
}
if err := gh.authenticate(w, r); err != nil {
handleError(unauthorizedError(err.Error()), w, r)
return
}
endpoint := config.GitHub.Endpoint
apiURL := singleJoiningSlash(endpoint, "/repos/"+config.GitHub.Repo)
target, err := url.Parse(apiURL)
if err != nil {
handleError(internalServerError("Unable to process GitHub endpoint"), w, r)
return
}
ctx = withProxyTarget(ctx, target)
ctx = withAccessToken(ctx, config.GitHub.AccessToken)
gh.proxy.ServeHTTP(w, r.WithContext(ctx))
}
func (gh *GitHubGateway) authenticate(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
claims := getClaims(ctx)
config := getConfig(ctx)
if claims == nil {
return errors.New("Access to endpoint not allowed: no claims found in Bearer token")
}
if !allowedRegexp.MatchString(r.URL.Path) {
return errors.New("Access to endpoint not allowed: this part of GitHub's API has been restricted")
}
if len(config.Roles) == 0 {
return nil
}
roles, ok := claims.AppMetaData["roles"]
if ok {
roleStrings, _ := roles.([]interface{})
for _, data := range roleStrings {
role, _ := data.(string)
for _, adminRole := range config.Roles {
if role == adminRole {
return nil
}
}
}
}
return errors.New("Access to endpoint not allowed: your role doesn't allow access")
}
type GitHubTransport struct{}
func (t *GitHubTransport) RoundTrip(r *http.Request) (*http.Response, error) {
resp, err := http.DefaultTransport.RoundTrip(r)
if err == nil {
// remove CORS headers from GitHub and use our own
resp.Header.Del("Access-Control-Allow-Origin")
}
return resp, err
}