X Tutup
Skip to content

Commit 55d3130

Browse files
committed
Have admin:org scope satisfy read:org requirement
`admin:org` is inclusive of `read:org`, so if we find the former listed in response headers, we can conclude that the token has necessary scopes instead of letting a warning notice be shown.
1 parent 53ff384 commit 55d3130

File tree

2 files changed

+86
-3
lines changed

2 files changed

+86
-3
lines changed

api/client.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ var issuedScopesWarning bool
9191

9292
// CheckScopes checks whether an OAuth scope is present in a response
9393
func CheckScopes(wantedScope string, cb func(string) error) ClientOption {
94+
wantedCandidates := []string{wantedScope}
95+
if strings.HasPrefix(wantedScope, "read:") {
96+
wantedCandidates = append(wantedCandidates, "admin:"+strings.TrimPrefix(wantedScope, "read:"))
97+
}
98+
9499
return func(tr http.RoundTripper) http.RoundTripper {
95100
return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
96101
res, err := tr.RoundTrip(req)
@@ -102,10 +107,13 @@ func CheckScopes(wantedScope string, cb func(string) error) ClientOption {
102107
hasScopes := strings.Split(res.Header.Get("X-Oauth-Scopes"), ",")
103108

104109
hasWanted := false
110+
outer:
105111
for _, s := range hasScopes {
106-
if wantedScope == strings.TrimSpace(s) {
107-
hasWanted = true
108-
break
112+
for _, w := range wantedCandidates {
113+
if w == strings.TrimSpace(s) {
114+
hasWanted = true
115+
break outer
116+
}
109117
}
110118
}
111119

api/client_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"errors"
66
"io/ioutil"
7+
"net/http"
78
"reflect"
89
"testing"
910

@@ -91,5 +92,79 @@ func TestRESTError(t *testing.T) {
9192
}
9293
if httpErr.Error() != "HTTP 422: OH NO (https://api.github.com/repos/branch)" {
9394
t.Errorf("got %q", httpErr.Error())
95+
96+
}
97+
}
98+
99+
func Test_CheckScopes(t *testing.T) {
100+
tests := []struct {
101+
name string
102+
wantScope string
103+
responseApp string
104+
responseScopes string
105+
expectCallback bool
106+
}{
107+
{
108+
name: "missing read:org",
109+
wantScope: "read:org",
110+
responseApp: "APPID",
111+
responseScopes: "repo, gist",
112+
expectCallback: true,
113+
},
114+
{
115+
name: "has read:org",
116+
wantScope: "read:org",
117+
responseApp: "APPID",
118+
responseScopes: "repo, read:org, gist",
119+
expectCallback: false,
120+
},
121+
{
122+
name: "has admin:org",
123+
wantScope: "read:org",
124+
responseApp: "APPID",
125+
responseScopes: "repo, admin:org, gist",
126+
expectCallback: false,
127+
},
128+
}
129+
for _, tt := range tests {
130+
t.Run(tt.name, func(t *testing.T) {
131+
tr := &httpmock.Registry{}
132+
tr.Register(httpmock.MatchAny, func(*http.Request) (*http.Response, error) {
133+
return &http.Response{
134+
StatusCode: 200,
135+
Header: http.Header{
136+
"X-Oauth-Client-Id": []string{tt.responseApp},
137+
"X-Oauth-Scopes": []string{tt.responseScopes},
138+
},
139+
}, nil
140+
})
141+
142+
callbackInvoked := false
143+
var gotAppID string
144+
fn := CheckScopes(tt.wantScope, func(appID string) error {
145+
callbackInvoked = true
146+
gotAppID = appID
147+
return nil
148+
})
149+
150+
rt := fn(tr)
151+
req, err := http.NewRequest("GET", "https://api.github.com/hello", nil)
152+
if err != nil {
153+
t.Fatalf("unexpected error: %v", err)
154+
}
155+
156+
issuedScopesWarning = false
157+
_, err = rt.RoundTrip(req)
158+
if err != nil {
159+
t.Fatalf("unexpected error: %v", err)
160+
}
161+
162+
if tt.expectCallback != callbackInvoked {
163+
t.Fatalf("expected CheckScopes callback: %v", tt.expectCallback)
164+
}
165+
if tt.expectCallback && gotAppID != tt.responseApp {
166+
t.Errorf("unexpected app ID: %q", gotAppID)
167+
}
168+
})
94169
}
95170
}

0 commit comments

Comments
 (0)
X Tutup