Skip to content

Commit ecef184

Browse files
committed
fix: Fix base url regression to ensure trailing /
Signed-off-by: Steve Hipwell <[email protected]>
1 parent a3b39ed commit ecef184

9 files changed

Lines changed: 251 additions & 217 deletions

github/apps.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"io"
1010
"net/http"
1111
"net/url"
12-
"path"
1312
"time"
1413

1514
"github.com/go-jose/go-jose/v3"
@@ -18,29 +17,22 @@ import (
1817

1918
// GenerateOAuthTokenFromApp generates a GitHub OAuth access token from a set of valid GitHub App credentials.
2019
// The returned token can be used to interact with both GitHub's REST and GraphQL APIs.
21-
func GenerateOAuthTokenFromApp(baseURL *url.URL, appID, appInstallationID, pemData string) (string, error) {
20+
func GenerateOAuthTokenFromApp(apiURL *url.URL, appID, appInstallationID, pemData string) (string, error) {
2221
appJWT, err := generateAppJWT(appID, time.Now(), []byte(pemData))
2322
if err != nil {
2423
return "", err
2524
}
2625

27-
token, err := getInstallationAccessToken(baseURL, appJWT, appInstallationID)
26+
token, err := getInstallationAccessToken(apiURL, appJWT, appInstallationID)
2827
if err != nil {
2928
return "", err
3029
}
3130

3231
return token, nil
3332
}
3433

35-
func getInstallationAccessToken(baseURL *url.URL, jwt, installationID string) (string, error) {
36-
hostname := baseURL.Hostname()
37-
if hostname != DotComHost && !GHECDataResidencyHostMatch.MatchString(hostname) {
38-
baseURL.Path = path.Join(baseURL.Path, "api/v3/")
39-
}
40-
41-
baseURL.Path = path.Join(baseURL.Path, "app/installations/", installationID, "access_tokens")
42-
43-
req, err := http.NewRequest(http.MethodPost, baseURL.String(), nil)
34+
func getInstallationAccessToken(apiURL *url.URL, jwt, installationID string) (string, error) {
35+
req, err := http.NewRequest(http.MethodPost, apiURL.JoinPath("installations", installationID, "access_tokens").String(), nil)
4436
if err != nil {
4537
return "", err
4638
}

github/config.go

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"net/http"
77
"net/url"
8-
"path"
98
"regexp"
109
"strings"
1110
"time"
@@ -19,7 +18,8 @@ import (
1918
type Config struct {
2019
Token string
2120
Owner string
22-
BaseURL string
21+
BaseURL *url.URL
22+
IsGHES bool
2323
Insecure bool
2424
WriteDelay time.Duration
2525
ReadDelay time.Duration
@@ -38,12 +38,27 @@ type Owner struct {
3838
IsOrganization bool
3939
}
4040

41-
// DotComHost is the hostname for GitHub.com API.
42-
const DotComHost = "api.github.com"
41+
const (
42+
// DotComAPIURL is the base API URL for github.com.
43+
DotComAPIURL = "https://api.github.com/"
44+
// DotComHost is the hostname for github.com.
45+
DotComHost = "github.com"
46+
// DotComAPIHost is the API hostname for github.com.
47+
DotComAPIHost = "api.github.com"
48+
// DotComRESTAPISuffix is the GraphQL suffix for github.com.
49+
DotComGraphQLPath = "graphql/"
50+
// GHESRESTAPISuffix is the rest api suffix for GitHub Enterprise Server.
51+
GHESRESTAPIPath = "api/v3/"
52+
// GHESGraphQLSuffix is the GraphQL suffix for GitHub Enterprise Server.
53+
GHESGraphQLPath = "api/graphql/"
54+
)
4355

44-
// GHECDataResidencyHostMatch is a regex to match a GitHub Enterprise Cloud data residency host:
45-
// https://[hostname].ghe.com/ instances expect paths that behave similar to GitHub.com, not GitHub Enterprise Server.
46-
var GHECDataResidencyHostMatch = regexp.MustCompile(`^[a-zA-Z0-9.\-]+\.ghe\.com\/?$`)
56+
var (
57+
// GHECHostMatch is a regex to match GitHub Enterprise Cloud hosts.
58+
GHECHostMatch = regexp.MustCompile(`\.ghe\.com$`)
59+
// GHECAPIHostMatch is a regex to match GitHub Enterprise Cloud API hosts.
60+
GHECAPIHostMatch = regexp.MustCompile(`^api\.[a-zA-Z0-9-]+\.ghe\.com$`)
61+
)
4762

4863
func RateLimitedHTTPClient(client *http.Client, writeDelay, readDelay, retryDelay time.Duration, parallelRequests bool, retryableErrors map[int]bool, maxRetries int) *http.Client {
4964
client.Transport = NewEtagTransport(client.Transport)
@@ -81,38 +96,24 @@ func (c *Config) AnonymousHTTPClient() *http.Client {
8196
}
8297

8398
func (c *Config) NewGraphQLClient(client *http.Client) (*githubv4.Client, error) {
84-
uv4, err := url.Parse(c.BaseURL)
85-
if err != nil {
86-
return nil, err
87-
}
88-
89-
hostname := uv4.Hostname()
90-
if hostname != DotComHost && !GHECDataResidencyHostMatch.MatchString(hostname) {
91-
uv4.Path = path.Join(uv4.Path, "api/graphql/")
99+
var path string
100+
if c.IsGHES {
101+
path = GHESGraphQLPath
92102
} else {
93-
uv4.Path = path.Join(uv4.Path, "graphql")
103+
path = DotComGraphQLPath
94104
}
95105

96-
return githubv4.NewEnterpriseClient(uv4.String(), client), nil
106+
return githubv4.NewEnterpriseClient(c.BaseURL.JoinPath(path).String(), client), nil
97107
}
98108

99109
func (c *Config) NewRESTClient(client *http.Client) (*github.Client, error) {
100-
uv3, err := url.Parse(c.BaseURL)
101-
if err != nil {
102-
return nil, err
110+
path := ""
111+
if c.IsGHES {
112+
path = GHESRESTAPIPath
103113
}
104114

105-
hostname := uv3.Hostname()
106-
if hostname != DotComHost && !GHECDataResidencyHostMatch.MatchString(hostname) {
107-
uv3.Path = fmt.Sprintf("%s/", path.Join(uv3.Path, "api/v3"))
108-
}
109-
110-
v3client, err := github.NewClient(client).WithEnterpriseURLs(uv3.String(), "")
111-
if err != nil {
112-
return nil, err
113-
}
114-
115-
v3client.BaseURL = uv3
115+
v3client := github.NewClient(client)
116+
v3client.BaseURL = c.BaseURL.JoinPath(path)
116117

117118
return v3client, nil
118119
}
@@ -199,3 +200,45 @@ func (injector *previewHeaderInjectorTransport) RoundTrip(req *http.Request) (*h
199200
}
200201
return injector.rt.RoundTrip(req)
201202
}
203+
204+
// getBaseURL returns a correctly configured base URL and a bool as to if this is GitHub Enterprise Server.
205+
func getBaseURL(s string) (*url.URL, bool, error) {
206+
if len(s) == 0 {
207+
s = DotComAPIURL
208+
}
209+
210+
u, err := url.Parse(s)
211+
if err != nil {
212+
return nil, false, err
213+
}
214+
215+
if !u.IsAbs() {
216+
return nil, false, fmt.Errorf("base url must be absolute")
217+
}
218+
219+
u = u.JoinPath("/")
220+
221+
switch {
222+
case u.Host == DotComAPIHost:
223+
case u.Host == DotComHost:
224+
u.Host = DotComAPIHost
225+
case GHECAPIHostMatch.MatchString(u.Host):
226+
case GHECHostMatch.MatchString(u.Host):
227+
u.Host = fmt.Sprintf("api.%s", u.Host)
228+
default:
229+
u.Path = strings.TrimSuffix(u.Path, GHESRESTAPIPath)
230+
return u, true, nil
231+
}
232+
233+
if u.Scheme != "https" {
234+
return nil, false, fmt.Errorf("base url for github.com or ghe.com must use the https scheme")
235+
}
236+
237+
if len(u.Path) > 1 {
238+
return nil, false, fmt.Errorf("base url for github.com or ghe.com must not contain a path, got %s", u.Path)
239+
}
240+
241+
u.Path = "/"
242+
243+
return u, false, nil
244+
}

0 commit comments

Comments
 (0)