Skip to content

Commit ce77fe6

Browse files
authored
Merge pull request #7 from jamierocks/server
Support third-party OAuth hosts
2 parents 63e88b3 + 5b3cff9 commit ce77fe6

7 files changed

+83
-31
lines changed

api/form.go

+24-12
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package api
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"io"
67
"io/ioutil"
8+
"mime"
79
"net/http"
810
"net/url"
9-
"strings"
11+
"strconv"
1012
)
1113

1214
type httpClient interface {
@@ -71,7 +73,9 @@ func PostForm(c httpClient, u string, params url.Values) (*FormResponse, error)
7173
requestURI: u,
7274
}
7375

74-
if contentType(resp.Header.Get("Content-Type")) == formType {
76+
mediaType, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
77+
switch mediaType {
78+
case "application/x-www-form-urlencoded":
7579
var bb []byte
7680
bb, err = ioutil.ReadAll(resp.Body)
7781
if err != nil {
@@ -82,7 +86,24 @@ func PostForm(c httpClient, u string, params url.Values) (*FormResponse, error)
8286
if err != nil {
8387
return r, err
8488
}
85-
} else {
89+
case "application/json":
90+
var values map[string]interface{}
91+
if err := json.NewDecoder(resp.Body).Decode(&values); err != nil {
92+
return r, err
93+
}
94+
95+
r.values = make(url.Values)
96+
for key, value := range values {
97+
switch v := value.(type) {
98+
case string:
99+
r.values.Set(key, v)
100+
case int64:
101+
r.values.Set(key, strconv.FormatInt(v, 10))
102+
case float64:
103+
r.values.Set(key, strconv.FormatFloat(v, 'f', -1, 64))
104+
}
105+
}
106+
default:
86107
_, err = io.Copy(ioutil.Discard, resp.Body)
87108
if err != nil {
88109
return r, err
@@ -91,12 +112,3 @@ func PostForm(c httpClient, u string, params url.Values) (*FormResponse, error)
91112

92113
return r, nil
93114
}
94-
95-
const formType = "application/x-www-form-urlencoded"
96-
97-
func contentType(t string) string {
98-
if i := strings.IndexRune(t, ';'); i >= 0 {
99-
return t[0:i]
100-
}
101-
return t
102-
}

api/form_test.go

+21-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func TestPostForm(t *testing.T) {
141141
wantErr bool
142142
}{
143143
{
144-
name: "success",
144+
name: "success urlencoded",
145145
args: args{
146146
url: "https://github.com/oauth",
147147
},
@@ -160,6 +160,26 @@ func TestPostForm(t *testing.T) {
160160
},
161161
wantErr: false,
162162
},
163+
{
164+
name: "success JSON",
165+
args: args{
166+
url: "https://github.com/oauth",
167+
},
168+
http: apiClient{
169+
body: `{"access_token":"123abc", "scopes":"repo gist"}`,
170+
status: 200,
171+
contentType: "application/json; charset=utf-8",
172+
},
173+
want: &FormResponse{
174+
StatusCode: 200,
175+
requestURI: "https://github.com/oauth",
176+
values: url.Values{
177+
"access_token": {"123abc"},
178+
"scopes": {"repo gist"},
179+
},
180+
},
181+
wantErr: false,
182+
},
163183
{
164184
name: "HTML response",
165185
args: args{

examples_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
// flow support is globally available, but enables logging in to hosted GitHub instances as well.
1111
func Example() {
1212
flow := &Flow{
13-
Hostname: "github.com",
13+
Host: GitHubHost("https://github.com"),
1414
ClientID: os.Getenv("OAUTH_CLIENT_ID"),
1515
ClientSecret: os.Getenv("OAUTH_CLIENT_SECRET"), // only applicable to web app flow
1616
CallbackURI: "http://127.0.0.1/callback", // only applicable to web app flow

go.sum

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
github.com/cli/browser v1.0.0 h1:RIleZgXrhdiCVgFBSjtWwkLPUCWyhhhN5k5HGSBt1js=
22
github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q=
3+
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
34
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=

oauth.go

+23-13
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,32 @@ type httpClient interface {
1717
PostForm(string, url.Values) (*http.Response, error)
1818
}
1919

20+
// Host defines the endpoints used to authorize against an OAuth server.
21+
type Host struct {
22+
DeviceCodeURL string
23+
AuthorizeURL string
24+
TokenURL string
25+
}
26+
27+
// GitHubHost constructs a Host from the given URL to a GitHub instance.
28+
func GitHubHost(hostURL string) *Host {
29+
u, _ := url.Parse(hostURL)
30+
31+
return &Host{
32+
DeviceCodeURL: fmt.Sprintf("%s://%s/login/device/code", u.Scheme, u.Host),
33+
AuthorizeURL: fmt.Sprintf("%s://%s/login/oauth/authorize", u.Scheme, u.Host),
34+
TokenURL: fmt.Sprintf("%s://%s/login/oauth/access_token", u.Scheme, u.Host),
35+
}
36+
}
37+
2038
// Flow facilitates a single OAuth authorization flow.
2139
type Flow struct {
22-
// The host to authorize the app with.
40+
// The hostname to authorize the app with.
41+
//
42+
// Deprecated: Use Host instead.
2343
Hostname string
44+
// Host configuration to authorize the app with.
45+
Host *Host
2446
// OAuth scopes to request from the user.
2547
Scopes []string
2648
// OAuth application ID.
@@ -47,18 +69,6 @@ type Flow struct {
4769
Stdout io.Writer
4870
}
4971

50-
func deviceInitURL(host string) string {
51-
return fmt.Sprintf("https://%s/login/device/code", host)
52-
}
53-
54-
func webappInitURL(host string) string {
55-
return fmt.Sprintf("https://%s/login/oauth/authorize", host)
56-
}
57-
58-
func tokenURL(host string) string {
59-
return fmt.Sprintf("https://%s/login/oauth/access_token", host)
60-
}
61-
6272
// DetectFlow tries to perform Device flow first and falls back to Web application flow.
6373
func (oa *Flow) DetectFlow() (*api.AccessToken, error) {
6474
accessToken, err := oa.DeviceFlow()

oauth_device.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ func (oa *Flow) DeviceFlow() (*api.AccessToken, error) {
2828
if stdout == nil {
2929
stdout = os.Stdout
3030
}
31+
host := oa.Host
32+
if host == nil {
33+
host = GitHubHost("https://" + oa.Hostname)
34+
}
3135

32-
code, err := device.RequestCode(httpClient, deviceInitURL(oa.Hostname), oa.ClientID, oa.Scopes)
36+
code, err := device.RequestCode(httpClient, host.DeviceCodeURL, oa.ClientID, oa.Scopes)
3337
if err != nil {
3438
return nil, err
3539
}
@@ -54,7 +58,7 @@ func (oa *Flow) DeviceFlow() (*api.AccessToken, error) {
5458
return nil, fmt.Errorf("error opening the web browser: %w", err)
5559
}
5660

57-
return device.PollToken(httpClient, tokenURL(oa.Hostname), oa.ClientID, code)
61+
return device.PollToken(httpClient, host.TokenURL, oa.ClientID, code)
5862
}
5963

6064
func waitForEnter(r io.Reader) error {

oauth_webapp.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import (
1212
// WebAppFlow starts a local HTTP server, opens the web browser to initiate the OAuth Web application
1313
// flow, blocks until the user completes authorization and is redirected back, and returns the access token.
1414
func (oa *Flow) WebAppFlow() (*api.AccessToken, error) {
15+
host := oa.Host
16+
if host == nil {
17+
host = GitHubHost("https://" + oa.Hostname)
18+
}
19+
1520
flow, err := webapp.InitFlow()
1621
if err != nil {
1722
return nil, err
@@ -23,7 +28,7 @@ func (oa *Flow) WebAppFlow() (*api.AccessToken, error) {
2328
Scopes: oa.Scopes,
2429
AllowSignup: true,
2530
}
26-
browserURL, err := flow.BrowserURL(webappInitURL(oa.Hostname), params)
31+
browserURL, err := flow.BrowserURL(host.AuthorizeURL, params)
2732
if err != nil {
2833
return nil, err
2934
}
@@ -47,5 +52,5 @@ func (oa *Flow) WebAppFlow() (*api.AccessToken, error) {
4752
httpClient = http.DefaultClient
4853
}
4954

50-
return flow.AccessToken(httpClient, tokenURL(oa.Hostname), oa.ClientSecret)
55+
return flow.AccessToken(httpClient, host.TokenURL, oa.ClientSecret)
5156
}

0 commit comments

Comments
 (0)