git.iko.soy/typeable/oauth2-proxy
on master help sign in
622 lines 19 kB history raw
1package providers
3import (
4 "context"
5 "net/http"
6 "net/http/httptest"
7 "net/url"
8 "testing"
10 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
12 . "github.com/onsi/gomega"
13 "github.com/stretchr/testify/assert"
16func testGitHubProvider(hostname string, opts options.GitHubOptions) *GitHubProvider {
17 p := NewGitHubProvider(
18 &ProviderData{
19 ProviderName: "",
20 LoginURL: &url.URL{},
21 RedeemURL: &url.URL{},
22 ProfileURL: &url.URL{},
23 ValidateURL: &url.URL{},
24 Scope: ""},
25 opts)
26 if hostname != "" {
27 updateURL(p.Data().LoginURL, hostname)
28 updateURL(p.Data().RedeemURL, hostname)
29 updateURL(p.Data().ProfileURL, hostname)
30 updateURL(p.Data().ValidateURL, hostname)
31 }
32 return p
35func testGitHubBackend(payloads map[string][]string) *httptest.Server {
36 pathToQueryMap := map[string][]string{
37 "/": {""},
38 "/repos/oauth2-proxy/oauth2-proxy": {""},
39 "/repos/oauth2-proxy/oauth2-proxy/collaborators/mbland": {""},
40 "/user": {""},
41 "/user/emails": {""},
42 "/user/teams": {"page=1&per_page=100", "page=2&per_page=100", "page=3&per_page=100"},
43 "/user/orgs": {"page=1&per_page=100", "page=2&per_page=100", "page=3&per_page=100"},
44 // GitHub Enterprise Server API
45 "/api/v3": {""},
46 "/api/v3/user/emails": {""},
47 }
49 return httptest.NewServer(http.HandlerFunc(
50 func(w http.ResponseWriter, r *http.Request) {
51 query, ok := pathToQueryMap[r.URL.Path]
52 validQuery := false
53 index := 0
54 for i, q := range query {
55 if q == r.URL.RawQuery {
56 validQuery = true
57 index = i
58 }
59 }
60 payload := []string{}
61 if ok && validQuery {
62 payload, ok = payloads[r.URL.Path]
63 }
64 if !ok {
65 w.WriteHeader(404)
66 } else if !validQuery {
67 w.WriteHeader(404)
68 } else if payload[index] == "" {
69 w.WriteHeader(204)
70 } else {
71 w.WriteHeader(200)
72 w.Write([]byte(payload[index]))
73 }
74 }))
77func TestNewGitHubProvider(t *testing.T) {
78 g := NewWithT(t)
80 // Test that defaults are set when calling for a new provider with nothing set
81 providerData := NewGitHubProvider(&ProviderData{}, options.GitHubOptions{}).Data()
82 g.Expect(providerData.ProviderName).To(Equal("GitHub"))
83 g.Expect(providerData.LoginURL.String()).To(Equal(githubDefaultLoginURL.String()))
84 g.Expect(providerData.RedeemURL.String()).To(Equal(githubDefaultRedeemURL.String()))
85 g.Expect(providerData.ProfileURL.String()).To(Equal(""))
86 g.Expect(providerData.ValidateURL.String()).To(Equal("https://api.github.com/"))
87 g.Expect(providerData.Scope).To(Equal("user:email read:org"))
90func TestGitHubProviderOverrides(t *testing.T) {
91 p := NewGitHubProvider(
92 &ProviderData{
93 LoginURL: &url.URL{
94 Scheme: "https",
95 Host: "example.com",
96 Path: "/login/oauth/authorize"},
97 RedeemURL: &url.URL{
98 Scheme: "https",
99 Host: "example.com",
100 Path: "/login/oauth/access_token"},
101 ValidateURL: &url.URL{
102 Scheme: "https",
103 Host: "api.example.com",
104 Path: "/"},
105 Scope: "profile"},
106 options.GitHubOptions{})
107 assert.NotEqual(t, nil, p)
108 assert.Equal(t, "GitHub", p.Data().ProviderName)
109 assert.Equal(t, "https://example.com/login/oauth/authorize",
110 p.Data().LoginURL.String())
111 assert.Equal(t, "https://example.com/login/oauth/access_token",
112 p.Data().RedeemURL.String())
113 assert.Equal(t, "https://api.example.com/",
114 p.Data().ValidateURL.String())
115 assert.Equal(t, "profile", p.Data().Scope)
118func TestGitHubProvider_getEmail(t *testing.T) {
119 b := testGitHubBackend(map[string][]string{
120 "/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
121 })
122 defer b.Close()
124 bURL, _ := url.Parse(b.URL)
125 p := testGitHubProvider(bURL.Host, options.GitHubOptions{})
127 session := CreateAuthorizedSession()
128 err := p.getEmail(context.Background(), session)
129 assert.NoError(t, err)
130 assert.Equal(t, "michael.bland@gsa.gov", session.Email)
133func TestGitHubProvider_getEmailNotVerified(t *testing.T) {
134 b := testGitHubBackend(map[string][]string{
135 "/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": false, "primary": true} ]`},
136 })
137 defer b.Close()
139 bURL, _ := url.Parse(b.URL)
140 p := testGitHubProvider(bURL.Host, options.GitHubOptions{})
142 session := CreateAuthorizedSession()
143 err := p.getEmail(context.Background(), session)
144 assert.NoError(t, err)
145 assert.Empty(t, session.Email)
148func TestGitHubProvider_getEmailWithOrg(t *testing.T) {
149 b := testGitHubBackend(map[string][]string{
150 "/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
151 "/user/orgs": {
152 `[ {"login":"testorg"} ]`,
153 `[ {"login":"testorg1"} ]`,
154 `[ ]`,
155 },
156 })
157 defer b.Close()
159 bURL, _ := url.Parse(b.URL)
160 p := testGitHubProvider(bURL.Host,
161 options.GitHubOptions{
162 Org: "testorg1",
163 },
164 )
166 session := CreateAuthorizedSession()
167 err := p.getEmail(context.Background(), session)
168 assert.NoError(t, err)
169 assert.Equal(t, "michael.bland@gsa.gov", session.Email)
172func TestGitHubProvider_checkRestrictionsOrg(t *testing.T) {
173 b := testGitHubBackend(map[string][]string{
174 "/user/emails": {`[ {"email": "john.doe@example", "verified": true, "primary": true} ]`},
175 "/user/orgs": {
176 `[ { "login": "test-org-1" } ]`,
177 `[ { "login": "test-org-2" } ]`,
178 `[ ]`,
179 },
180 "/user/teams": {
181 `[ { "name":"test-team-1", "slug":"test-team-1", "organization": { "login": "test-org-1" } } ]`,
182 `[ { "name":"test-team-2", "slug":"test-team-2", "organization": { "login": "test-org-2" } } ]`,
183 `[ ]`,
184 },
185 })
186 defer b.Close()
188 // This test should succeed, because of the valid organization.
189 bURL, _ := url.Parse(b.URL)
190 p := testGitHubProvider(bURL.Host,
191 options.GitHubOptions{
192 Org: "test-org-1",
193 },
194 )
196 var err error
197 session := CreateAuthorizedSession()
198 err = p.getOrgAndTeam(context.Background(), session)
199 assert.NoError(t, err)
200 err = p.checkRestrictions(context.Background(), session)
201 assert.NoError(t, err)
203 // This part should fail, because user is not part of the organization
204 p = testGitHubProvider(bURL.Host,
205 options.GitHubOptions{
206 Org: "test-org-1-fail",
207 },
208 )
210 err = p.checkRestrictions(context.Background(), session)
211 assert.Error(t, err)
214func TestGitHubProvider_checkRestrictionsOrgTeam(t *testing.T) {
215 b := testGitHubBackend(map[string][]string{
216 "/user/emails": {`[ {"email": "john.doe@example", "verified": true, "primary": true} ]`},
217 "/user/orgs": {
218 `[ { "login": "test-org-1" } ]`,
219 `[ { "login": "test-org-2" } ]`,
220 `[ ]`,
221 },
222 "/user/teams": {
223 `[ { "name":"test-team-1", "slug":"test-team-1", "organization": { "login": "test-org-1" } } ]`,
224 `[ { "name":"test-team-2", "slug":"test-team-2", "organization": { "login": "test-org-2" } } ]`,
225 `[ ]`,
226 },
227 })
228 defer b.Close()
230 // This test should succeed, because of the valid Org and Team combination.
231 bURL, _ := url.Parse(b.URL)
232 p := testGitHubProvider(bURL.Host,
233 options.GitHubOptions{
234 Org: "test-org-1",
235 Team: "test-team-1",
236 },
237 )
239 var err error
240 session := CreateAuthorizedSession()
241 err = p.getOrgAndTeam(context.Background(), session)
242 assert.NoError(t, err)
243 err = p.checkRestrictions(context.Background(), session)
244 assert.NoError(t, err)
246 // This part should fail, because user is not part of the organization and the team
247 p = testGitHubProvider(bURL.Host,
248 options.GitHubOptions{
249 Org: "test-org-1-fail",
250 Team: "test-team-1-fail",
251 },
252 )
254 err = p.checkRestrictions(context.Background(), session)
255 assert.Error(t, err)
258func TestGitHubProvider_checkRestrictionsTeam(t *testing.T) {
259 b := testGitHubBackend(map[string][]string{
260 "/user/emails": {`[ {"email": "john.doe@example", "verified": true, "primary": true} ]`},
261 "/user/orgs": {
262 `[ { "login": "test-org-1" } ]`,
263 `[ { "login": "test-org-2" } ]`,
264 `[ ]`,
265 },
266 "/user/teams": {
267 `[ { "name":"test-team-1", "slug":"test-team-1", "organization": { "login": "test-org-1" } } ]`,
268 `[ { "name":"test-team-2", "slug":"test-team-2", "organization": { "login": "test-org-2" } } ]`,
269 `[ ]`,
270 },
271 })
272 defer b.Close()
274 // This test should succeed, because of the valid org:slug combination.
275 bURL, _ := url.Parse(b.URL)
276 p := testGitHubProvider(bURL.Host,
277 options.GitHubOptions{
278 Team: "test-org-1:test-team-1, test-org-2:test-team-2-fail",
279 },
280 )
282 var err error
283 session := CreateAuthorizedSession()
284 err = p.getOrgAndTeam(context.Background(), session)
285 assert.NoError(t, err)
286 err = p.checkRestrictions(context.Background(), session)
287 assert.NoError(t, err)
289 // This part should fail, because user is not part of the organization:team combination
290 p = testGitHubProvider(bURL.Host,
291 options.GitHubOptions{
292 Team: "test-org-1-fail:test-team-1-fail, test-org-2-fail:test-team-2-fail",
293 },
294 )
296 err = p.checkRestrictions(context.Background(), session)
297 assert.Error(t, err)
300func TestGitHubProvider_getEmailWithWriteAccessToPublicRepo(t *testing.T) {
301 b := testGitHubBackend(map[string][]string{
302 "/repo/oauth2-proxy/oauth2-proxy": {`{"permissions": {"pull": true, "push": true}, "private": false}`},
303 "/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
304 })
305 defer b.Close()
307 bURL, _ := url.Parse(b.URL)
308 p := testGitHubProvider(bURL.Host,
309 options.GitHubOptions{
310 Repo: "oauth2-proxy/oauth2-proxy",
311 Token: "token",
312 },
313 )
315 session := CreateAuthorizedSession()
316 err := p.getEmail(context.Background(), session)
317 assert.NoError(t, err)
318 assert.Equal(t, "michael.bland@gsa.gov", session.Email)
321func TestGitHubProvider_getEmailWithReadOnlyAccessToPrivateRepo(t *testing.T) {
322 b := testGitHubBackend(map[string][]string{
323 "/repo/oauth2-proxy/oauth2-proxy": {`{"permissions": {"pull": true, "push": false}, "private": true}`},
324 "/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
325 })
326 defer b.Close()
328 bURL, _ := url.Parse(b.URL)
329 p := testGitHubProvider(bURL.Host,
330 options.GitHubOptions{
331 Repo: "oauth2-proxy/oauth2-proxy",
332 Token: "token",
333 },
334 )
336 session := CreateAuthorizedSession()
337 err := p.getEmail(context.Background(), session)
338 assert.NoError(t, err)
339 assert.Equal(t, "michael.bland@gsa.gov", session.Email)
342func TestGitHubProvider_getEmailWithWriteAccessToPrivateRepo(t *testing.T) {
343 b := testGitHubBackend(map[string][]string{
344 "/repo/oauth2-proxy/oauth2-proxy": {`{"permissions": {"pull": true, "push": true}, "private": true}`},
345 "/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
346 })
347 defer b.Close()
349 bURL, _ := url.Parse(b.URL)
350 p := testGitHubProvider(bURL.Host,
351 options.GitHubOptions{
352 Repo: "oauth2-proxy/oauth2-proxy",
353 Token: "token",
354 },
355 )
357 session := CreateAuthorizedSession()
358 err := p.getEmail(context.Background(), session)
359 assert.NoError(t, err)
360 assert.Equal(t, "michael.bland@gsa.gov", session.Email)
363func TestGitHubProvider_checkRestrictionsWithNoAccessToPrivateRepo(t *testing.T) {
364 b := testGitHubBackend(map[string][]string{
365 "/repos/oauth2-proxy/oauth2-proxy": {`{}`},
366 })
367 defer b.Close()
369 bURL, _ := url.Parse(b.URL)
370 p := testGitHubProvider(bURL.Host,
371 options.GitHubOptions{
372 Repo: "oauth2-proxy/oauth2-proxy",
373 },
374 )
376 session := CreateAuthorizedSession()
377 err := p.checkRestrictions(context.Background(), session)
378 assert.Error(t, err)
379 assert.Empty(t, session.Email)
382func TestGitHubProvider_getEmailWithToken(t *testing.T) {
383 b := testGitHubBackend(map[string][]string{
384 "/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
385 })
386 defer b.Close()
388 bURL, _ := url.Parse(b.URL)
389 p := testGitHubProvider(bURL.Host,
390 options.GitHubOptions{
391 Repo: "oauth2-proxy/oauth2-proxy",
392 Token: "token",
393 },
394 )
396 session := CreateAuthorizedSession()
397 err := p.getEmail(context.Background(), session)
398 assert.NoError(t, err)
399 assert.Equal(t, "michael.bland@gsa.gov", session.Email)
402// Note that trying to trigger the "failed building request" case is not
403// practical, since the only way it can fail is if the URL fails to parse.
404func TestGitHubProvider_getEmailFailedRequest(t *testing.T) {
405 b := testGitHubBackend(map[string][]string{})
406 defer b.Close()
408 bURL, _ := url.Parse(b.URL)
409 p := testGitHubProvider(bURL.Host, options.GitHubOptions{})
411 // We'll trigger a request failure by using an unexpected access
412 // token. Alternatively, we could allow the parsing of the payload as
413 // JSON to fail.
414 session := &sessions.SessionState{AccessToken: "unexpected_access_token"}
415 err := p.getEmail(context.Background(), session)
416 assert.Error(t, err)
417 assert.Empty(t, session.Email)
420func TestGitHubProvider_getEmailNotPresentInPayload(t *testing.T) {
421 b := testGitHubBackend(map[string][]string{
422 "/user/emails": {`{"foo": "bar"}`},
423 })
424 defer b.Close()
426 bURL, _ := url.Parse(b.URL)
427 p := testGitHubProvider(bURL.Host, options.GitHubOptions{})
429 session := CreateAuthorizedSession()
430 err := p.getEmail(context.Background(), session)
431 assert.Error(t, err)
432 assert.Empty(t, session.Email)
435func TestGitHubProvider_getUser(t *testing.T) {
436 b := testGitHubBackend(map[string][]string{
437 "/user": {`{"email": "michael.bland@gsa.gov", "login": "mbland"}`},
438 })
439 defer b.Close()
441 bURL, _ := url.Parse(b.URL)
442 p := testGitHubProvider(bURL.Host, options.GitHubOptions{})
444 session := CreateAuthorizedSession()
445 err := p.getUser(context.Background(), session)
446 assert.NoError(t, err)
447 assert.Equal(t, "mbland", session.User)
450func TestGitHubProvider_getUserWithRepoAndToken(t *testing.T) {
451 b := testGitHubBackend(map[string][]string{
452 "/user": {`{"email": "michael.bland@gsa.gov", "login": "mbland"}`},
453 "/repos/oauth2-proxy/oauth2-proxy/collaborators/mbland": {""},
454 })
455 defer b.Close()
457 bURL, _ := url.Parse(b.URL)
458 p := testGitHubProvider(bURL.Host,
459 options.GitHubOptions{
460 Repo: "oauth2-proxy/oauth2-proxy",
461 Token: "token",
462 },
463 )
465 session := CreateAuthorizedSession()
466 err := p.getUser(context.Background(), session)
467 assert.NoError(t, err)
468 assert.Equal(t, "mbland", session.User)
471func TestGitHubProvider_getUserWithRepoAndTokenWithoutPushAccess(t *testing.T) {
472 b := testGitHubBackend(map[string][]string{})
473 defer b.Close()
475 bURL, _ := url.Parse(b.URL)
476 p := testGitHubProvider(bURL.Host,
477 options.GitHubOptions{
478 Repo: "oauth2-proxy/oauth2-proxy",
479 Token: "token",
480 },
481 )
483 session := CreateAuthorizedSession()
484 err := p.getUser(context.Background(), session)
485 assert.Error(t, err)
486 assert.Empty(t, session.User)
489func TestGitHubProvider_getEmailWithUsername(t *testing.T) {
490 b := testGitHubBackend(map[string][]string{
491 "/user": {`{"email": "michael.bland@gsa.gov", "login": "mbland"}`},
492 "/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
493 })
494 defer b.Close()
496 bURL, _ := url.Parse(b.URL)
497 p := testGitHubProvider(bURL.Host,
498 options.GitHubOptions{
499 Users: []string{"mbland", "octocat"},
500 },
501 )
503 session := CreateAuthorizedSession()
504 err := p.getEmail(context.Background(), session)
505 assert.NoError(t, err)
506 assert.Equal(t, "michael.bland@gsa.gov", session.Email)
509func TestGitHubProvider_checkRestrictionsWithNotAllowedUsername(t *testing.T) {
510 b := testGitHubBackend(map[string][]string{
511 "/user": {`{"email": "michael.bland@gsa.gov", "login": "mbland"}`},
512 "/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
513 })
514 defer b.Close()
516 bURL, _ := url.Parse(b.URL)
517 p := testGitHubProvider(bURL.Host,
518 options.GitHubOptions{
519 Users: []string{"octocat"},
520 },
521 )
523 session := CreateAuthorizedSession()
524 err := p.checkRestrictions(context.Background(), session)
525 assert.Error(t, err)
526 assert.Empty(t, session.Email)
529func TestGitHubProvider_getEmailWithUsernameAndNotBelongToOrg(t *testing.T) {
530 b := testGitHubBackend(map[string][]string{
531 "/user": {`{"email": "michael.bland@gsa.gov", "login": "mbland"}`},
532 "/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
533 "/user/orgs": {
534 `[ {"login":"testorg"} ]`,
535 `[ ]`,
536 },
537 })
538 defer b.Close()
540 bURL, _ := url.Parse(b.URL)
541 p := testGitHubProvider(bURL.Host,
542 options.GitHubOptions{
543 Org: "not_belog_to",
544 Users: []string{"mbland"},
545 },
546 )
548 session := CreateAuthorizedSession()
549 err := p.getEmail(context.Background(), session)
550 assert.NoError(t, err)
551 assert.Equal(t, "michael.bland@gsa.gov", session.Email)
554func TestGitHubProvider_getEmailWithUsernameAndNoAccessToPrivateRepo(t *testing.T) {
555 b := testGitHubBackend(map[string][]string{
556 "/user": {`{"email": "michael.bland@gsa.gov", "login": "mbland"}`},
557 "/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
558 "/repo/oauth2-proxy/oauth2-proxy": {`{}`},
559 })
560 defer b.Close()
562 bURL, _ := url.Parse(b.URL)
563 p := testGitHubProvider(bURL.Host,
564 options.GitHubOptions{
565 Repo: "oauth2-proxy/oauth2-proxy",
566 Token: "token",
567 Users: []string{"mbland"},
568 },
569 )
571 session := CreateAuthorizedSession()
572 err := p.getEmail(context.Background(), session)
573 assert.NoError(t, err)
574 assert.Equal(t, "michael.bland@gsa.gov", session.Email)
577func TestGitHubProvider_ValidateSessionWithBaseUrl(t *testing.T) {
578 b := testGitHubBackend(map[string][]string{
579 "/": {`[]`},
580 })
581 defer b.Close()
583 bURL, _ := url.Parse(b.URL)
584 p := testGitHubProvider(bURL.Host, options.GitHubOptions{})
586 session := CreateAuthorizedSession()
588 valid := p.ValidateSession(context.Background(), session)
589 assert.True(t, valid)
592func TestGitHubProvider_ValidateSessionWithEnterpriseBaseUrl(t *testing.T) {
593 b := testGitHubBackend(map[string][]string{
594 "/api/v3": {`[]`},
595 })
596 defer b.Close()
598 bURL, _ := url.Parse(b.URL)
599 p := testGitHubProvider(bURL.Host, options.GitHubOptions{})
600 p.ValidateURL.Path = "/api/v3"
602 session := CreateAuthorizedSession()
604 valid := p.ValidateSession(context.Background(), session)
605 assert.True(t, valid)
608func TestGitHubProvider_ValidateSessionWithUserEmails(t *testing.T) {
609 b := testGitHubBackend(map[string][]string{
610 "/user/emails": {`[ {"email": "michael.bland@gsa.gov", "verified": true, "primary": true} ]`},
611 })
612 defer b.Close()
614 bURL, _ := url.Parse(b.URL)
615 p := testGitHubProvider(bURL.Host, options.GitHubOptions{})
616 p.ValidateURL.Path = "/user/emails"
618 session := CreateAuthorizedSession()
620 valid := p.ValidateSession(context.Background(), session)
621 assert.True(t, valid)