git.iko.soy/typeable/oauth2-proxy
on 8cbf921 help sign in
typeable/oauth2-proxy/oauthproxy_test.go
2346 lines 64 kB history raw
1package main
3import (
4 "bufio"
5 "context"
6 "crypto"
7 "encoding/base64"
8 "fmt"
9 "io"
10 "io/ioutil"
11 "net/http"
12 "net/http/httptest"
13 "net/url"
14 "os"
15 "regexp"
16 "strings"
17 "testing"
18 "time"
20 "github.com/coreos/go-oidc"
21 "github.com/mbland/hmacauth"
22 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
23 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
24 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
25 sessionscookie "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie"
26 "github.com/oauth2-proxy/oauth2-proxy/pkg/upstream"
27 "github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
28 "github.com/oauth2-proxy/oauth2-proxy/providers"
29 "github.com/stretchr/testify/assert"
30 "github.com/stretchr/testify/require"
33const (
34 // The rawCookieSecret is 32 bytes and the base64CookieSecret is the base64
35 // encoded version of this.
36 rawCookieSecret = "secretthirtytwobytes+abcdefghijk"
37 base64CookieSecret = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpams"
38 clientID = "3984n253984d7348dm8234yf982t"
39 clientSecret = "gv3498mfc9t23y23974dm2394dm9"
42func init() {
43 logger.SetFlags(logger.Lshortfile)
46func TestRobotsTxt(t *testing.T) {
47 opts := baseTestOptions()
48 err := validation.Validate(opts)
49 assert.NoError(t, err)
51 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
52 if err != nil {
53 t.Fatal(err)
54 }
55 rw := httptest.NewRecorder()
56 req, _ := http.NewRequest("GET", "/robots.txt", nil)
57 proxy.ServeHTTP(rw, req)
58 assert.Equal(t, 200, rw.Code)
59 assert.Equal(t, "User-agent: *\nDisallow: /", rw.Body.String())
62func TestIsValidRedirect(t *testing.T) {
63 opts := baseTestOptions()
64 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
65 opts.WhitelistDomains = []string{
66 "foo.bar",
67 ".bar.foo",
68 "port.bar:8080",
69 ".sub.port.bar:8080",
70 "anyport.bar:*",
71 ".sub.anyport.bar:*",
72 }
73 err := validation.Validate(opts)
74 assert.NoError(t, err)
76 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
77 if err != nil {
78 t.Fatal(err)
79 }
81 testCases := []struct {
82 Desc, Redirect string
83 ExpectedResult bool
84 }{
85 {
86 Desc: "noRD",
87 Redirect: "",
88 ExpectedResult: false,
89 },
90 {
91 Desc: "singleSlash",
92 Redirect: "/redirect",
93 ExpectedResult: true,
94 },
95 {
96 Desc: "doubleSlash",
97 Redirect: "//redirect",
98 ExpectedResult: false,
99 },
100 {
101 Desc: "validHTTP",
102 Redirect: "http://foo.bar/redirect",
103 ExpectedResult: true,
104 },
105 {
106 Desc: "validHTTPS",
107 Redirect: "https://foo.bar/redirect",
108 ExpectedResult: true,
109 },
110 {
111 Desc: "invalidHTTPSubdomain",
112 Redirect: "http://baz.foo.bar/redirect",
113 ExpectedResult: false,
114 },
115 {
116 Desc: "invalidHTTPSSubdomain",
117 Redirect: "https://baz.foo.bar/redirect",
118 ExpectedResult: false,
119 },
120 {
121 Desc: "validHTTPSubdomain",
122 Redirect: "http://baz.bar.foo/redirect",
123 ExpectedResult: true,
124 },
125 {
126 Desc: "validHTTPSSubdomain",
127 Redirect: "https://baz.bar.foo/redirect",
128 ExpectedResult: true,
129 },
130 {
131 Desc: "validHTTPDomain",
132 Redirect: "http://bar.foo/redirect",
133 ExpectedResult: true,
134 },
135 {
136 Desc: "invalidHTTP1",
137 Redirect: "http://foo.bar.evil.corp/redirect",
138 ExpectedResult: false,
139 },
140 {
141 Desc: "invalidHTTPS1",
142 Redirect: "https://foo.bar.evil.corp/redirect",
143 ExpectedResult: false,
144 },
145 {
146 Desc: "invalidHTTP2",
147 Redirect: "http://evil.corp/redirect?rd=foo.bar",
148 ExpectedResult: false,
149 },
150 {
151 Desc: "invalidHTTPS2",
152 Redirect: "https://evil.corp/redirect?rd=foo.bar",
153 ExpectedResult: false,
154 },
155 {
156 Desc: "invalidPort",
157 Redirect: "https://evil.corp:3838/redirect",
158 ExpectedResult: false,
159 },
160 {
161 Desc: "invalidEmptyPort",
162 Redirect: "http://foo.bar:3838/redirect",
163 ExpectedResult: false,
164 },
165 {
166 Desc: "invalidEmptyPortSubdomain",
167 Redirect: "http://baz.bar.foo:3838/redirect",
168 ExpectedResult: false,
169 },
170 {
171 Desc: "validSpecificPort",
172 Redirect: "http://port.bar:8080/redirect",
173 ExpectedResult: true,
174 },
175 {
176 Desc: "invalidSpecificPort",
177 Redirect: "http://port.bar:3838/redirect",
178 ExpectedResult: false,
179 },
180 {
181 Desc: "validSpecificPortSubdomain",
182 Redirect: "http://foo.sub.port.bar:8080/redirect",
183 ExpectedResult: true,
184 },
185 {
186 Desc: "invalidSpecificPortSubdomain",
187 Redirect: "http://foo.sub.port.bar:3838/redirect",
188 ExpectedResult: false,
189 },
190 {
191 Desc: "validAnyPort1",
192 Redirect: "http://anyport.bar:8080/redirect",
193 ExpectedResult: true,
194 },
195 {
196 Desc: "validAnyPort2",
197 Redirect: "http://anyport.bar:8081/redirect",
198 ExpectedResult: true,
199 },
200 {
201 Desc: "validAnyPortSubdomain1",
202 Redirect: "http://a.sub.anyport.bar:8080/redirect",
203 ExpectedResult: true,
204 },
205 {
206 Desc: "validAnyPortSubdomain2",
207 Redirect: "http://a.sub.anyport.bar:8081/redirect",
208 ExpectedResult: true,
209 },
210 {
211 Desc: "openRedirect1",
212 Redirect: "/\\evil.com",
213 ExpectedResult: false,
214 },
215 {
216 Desc: "openRedirectSpace1",
217 Redirect: "/ /evil.com",
218 ExpectedResult: false,
219 },
220 {
221 Desc: "openRedirectSpace2",
222 Redirect: "/ \\evil.com",
223 ExpectedResult: false,
224 },
225 {
226 Desc: "openRedirectTab1",
227 Redirect: "/\t/evil.com",
228 ExpectedResult: false,
229 },
230 {
231 Desc: "openRedirectTab2",
232 Redirect: "/\t\\evil.com",
233 ExpectedResult: false,
234 },
235 {
236 Desc: "openRedirectVerticalTab1",
237 Redirect: "/\v/evil.com",
238 ExpectedResult: false,
239 },
240 {
241 Desc: "openRedirectVerticalTab2",
242 Redirect: "/\v\\evil.com",
243 ExpectedResult: false,
244 },
245 {
246 Desc: "openRedirectNewLine1",
247 Redirect: "/\n/evil.com",
248 ExpectedResult: false,
249 },
250 {
251 Desc: "openRedirectNewLine2",
252 Redirect: "/\n\\evil.com",
253 ExpectedResult: false,
254 },
255 {
256 Desc: "openRedirectCarriageReturn1",
257 Redirect: "/\r/evil.com",
258 ExpectedResult: false,
259 },
260 {
261 Desc: "openRedirectCarriageReturn2",
262 Redirect: "/\r\\evil.com",
263 ExpectedResult: false,
264 },
265 {
266 Desc: "openRedirectTripleTab",
267 Redirect: "/\t\t/\t/evil.com",
268 ExpectedResult: false,
269 },
270 {
271 Desc: "openRedirectTripleTab2",
272 Redirect: "/\t\t\\\t/evil.com",
273 ExpectedResult: false,
274 },
275 {
276 Desc: "openRedirectQuadTab1",
277 Redirect: "/\t\t/\t\t\\evil.com",
278 ExpectedResult: false,
279 },
280 {
281 Desc: "openRedirectQuadTab2",
282 Redirect: "/\t\t\\\t\t/evil.com",
283 ExpectedResult: false,
284 },
285 {
286 Desc: "openRedirectPeriod1",
287 Redirect: "/./\\evil.com",
288 ExpectedResult: false,
289 },
290 {
291 Desc: "openRedirectPeriod2",
292 Redirect: "/./../../\\evil.com",
293 ExpectedResult: false,
294 },
295 {
296 Desc: "openRedirectDoubleTab",
297 Redirect: "/\t/\t\\evil.com",
298 ExpectedResult: false,
299 },
300 }
302 for _, tc := range testCases {
303 t.Run(tc.Desc, func(t *testing.T) {
304 result := proxy.IsValidRedirect(tc.Redirect)
306 if result != tc.ExpectedResult {
307 t.Errorf("expected %t got %t", tc.ExpectedResult, result)
308 }
309 })
310 }
313func TestOpenRedirects(t *testing.T) {
314 opts := baseTestOptions()
315 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
316 opts.WhitelistDomains = []string{
317 "foo.bar",
318 ".bar.foo",
319 "port.bar:8080",
320 ".sub.port.bar:8080",
321 "anyport.bar:*",
322 ".sub.anyport.bar:*",
323 "www.whitelisteddomain.tld",
324 }
325 err := validation.Validate(opts)
326 assert.NoError(t, err)
328 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
329 if err != nil {
330 t.Fatal(err)
331 }
333 file, err := os.Open("./test/openredirects.txt")
334 if err != nil {
335 t.Fatal(err)
336 }
337 defer func(t *testing.T) {
338 if err := file.Close(); err != nil {
339 t.Fatal(err)
340 }
341 }(t)
343 scanner := bufio.NewScanner(file)
344 for scanner.Scan() {
345 rd := scanner.Text()
346 t.Run(rd, func(t *testing.T) {
347 rdUnescaped, err := url.QueryUnescape(rd)
348 if err != nil {
349 t.Fatal(err)
350 }
351 if proxy.IsValidRedirect(rdUnescaped) {
352 t.Errorf("Expected %q to not be valid (unescaped: %q)", rd, rdUnescaped)
353 }
354 })
355 }
357 if err := scanner.Err(); err != nil {
358 t.Fatal(err)
359 }
362type TestProvider struct {
363 *providers.ProviderData
364 EmailAddress string
365 ValidToken bool
366 GroupValidator func(string) bool
369var _ providers.Provider = (*TestProvider)(nil)
371func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
372 return &TestProvider{
373 ProviderData: &providers.ProviderData{
374 ProviderName: "Test Provider",
375 LoginURL: &url.URL{
376 Scheme: "http",
377 Host: providerURL.Host,
378 Path: "/oauth/authorize",
379 },
380 RedeemURL: &url.URL{
381 Scheme: "http",
382 Host: providerURL.Host,
383 Path: "/oauth/token",
384 },
385 ProfileURL: &url.URL{
386 Scheme: "http",
387 Host: providerURL.Host,
388 Path: "/api/v1/profile",
389 },
390 Scope: "profile.email",
391 },
392 EmailAddress: emailAddress,
393 GroupValidator: func(s string) bool {
394 return true
395 },
396 }
399func (tp *TestProvider) GetEmailAddress(ctx context.Context, session *sessions.SessionState) (string, error) {
400 return tp.EmailAddress, nil
403func (tp *TestProvider) ValidateSessionState(ctx context.Context, session *sessions.SessionState) bool {
404 return tp.ValidToken
407func TestBasicAuthPassword(t *testing.T) {
408 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
409 logger.Printf("%#v", r)
410 var payload string
411 switch r.URL.Path {
412 case "/oauth/token":
413 payload = `{"access_token": "my_auth_token"}`
414 default:
415 payload = r.Header.Get("Authorization")
416 if payload == "" {
417 payload = "No Authorization header found."
418 }
419 }
420 w.WriteHeader(200)
421 _, err := w.Write([]byte(payload))
422 if err != nil {
423 t.Fatal(err)
424 }
425 }))
426 opts := baseTestOptions()
427 opts.UpstreamServers = options.Upstreams{
428 {
429 ID: providerServer.URL,
430 Path: "/",
431 URI: providerServer.URL,
432 },
433 }
435 opts.Cookie.Secure = false
436 opts.PassBasicAuth = true
437 opts.SetBasicAuth = true
438 opts.PassUserHeaders = true
439 opts.PreferEmailToUser = true
440 opts.BasicAuthPassword = "This is a secure password"
441 err := validation.Validate(opts)
442 assert.NoError(t, err)
444 providerURL, _ := url.Parse(providerServer.URL)
445 const emailAddress = "john.doe@example.com"
447 opts.SetProvider(NewTestProvider(providerURL, emailAddress))
448 proxy, err := NewOAuthProxy(opts, func(email string) bool {
449 return email == emailAddress
450 })
451 if err != nil {
452 t.Fatal(err)
453 }
455 rw := httptest.NewRecorder()
456 req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:", strings.NewReader(""))
457 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
458 proxy.ServeHTTP(rw, req)
459 if rw.Code >= 400 {
460 t.Fatalf("expected 3xx got %d", rw.Code)
461 }
462 cookie := rw.Header().Values("Set-Cookie")[1]
464 cookieName := proxy.CookieName
465 var value string
466 keyPrefix := cookieName + "="
468 for _, field := range strings.Split(cookie, "; ") {
469 value = strings.TrimPrefix(field, keyPrefix)
470 if value != field {
471 break
472 } else {
473 value = ""
474 }
475 }
477 req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
478 req.AddCookie(&http.Cookie{
479 Name: cookieName,
480 Value: value,
481 Path: "/",
482 Expires: time.Now().Add(time.Duration(24)),
483 HttpOnly: true,
484 })
485 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
487 rw = httptest.NewRecorder()
488 proxy.ServeHTTP(rw, req)
490 // The username in the basic auth credentials is expected to be equal to the email address from the
491 // auth response, so we use the same variable here.
492 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
493 assert.Equal(t, expectedHeader, rw.Body.String())
494 providerServer.Close()
497func TestBasicAuthWithEmail(t *testing.T) {
498 opts := baseTestOptions()
499 opts.PassBasicAuth = true
500 opts.PassUserHeaders = false
501 opts.PreferEmailToUser = false
502 opts.BasicAuthPassword = "This is a secure password"
503 err := validation.Validate(opts)
504 assert.NoError(t, err)
506 const emailAddress = "john.doe@example.com"
507 const userName = "9fcab5c9b889a557"
509 // The username in the basic auth credentials is expected to be equal to the email address from the
510 expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
511 expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
513 created := time.Now()
514 session := &sessions.SessionState{
515 User: userName,
516 Email: emailAddress,
517 AccessToken: "oauth_token",
518 CreatedAt: &created,
519 }
520 {
521 rw := httptest.NewRecorder()
522 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
523 proxy, err := NewOAuthProxy(opts, func(email string) bool {
524 return email == emailAddress
525 })
526 if err != nil {
527 t.Fatal(err)
528 }
529 proxy.addHeadersForProxying(rw, req, session)
530 assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
531 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
532 }
534 opts.PreferEmailToUser = true
535 {
536 rw := httptest.NewRecorder()
537 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
539 proxy, err := NewOAuthProxy(opts, func(email string) bool {
540 return email == emailAddress
541 })
542 if err != nil {
543 t.Fatal(err)
544 }
545 proxy.addHeadersForProxying(rw, req, session)
546 assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
547 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
548 }
551func TestPassUserHeadersWithEmail(t *testing.T) {
552 opts := baseTestOptions()
553 err := validation.Validate(opts)
554 assert.NoError(t, err)
556 const emailAddress = "john.doe@example.com"
557 const userName = "9fcab5c9b889a557"
559 created := time.Now()
560 session := &sessions.SessionState{
561 User: userName,
562 Email: emailAddress,
563 AccessToken: "oauth_token",
564 CreatedAt: &created,
565 }
566 {
567 rw := httptest.NewRecorder()
568 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
569 proxy, err := NewOAuthProxy(opts, func(email string) bool {
570 return email == emailAddress
571 })
572 if err != nil {
573 t.Fatal(err)
574 }
575 proxy.addHeadersForProxying(rw, req, session)
576 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
577 }
579 opts.PreferEmailToUser = true
580 {
581 rw := httptest.NewRecorder()
582 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
584 proxy, err := NewOAuthProxy(opts, func(email string) bool {
585 return email == emailAddress
586 })
587 if err != nil {
588 t.Fatal(err)
589 }
590 proxy.addHeadersForProxying(rw, req, session)
591 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
592 }
595func TestPassGroupsHeadersWithGroups(t *testing.T) {
596 opts := baseTestOptions()
597 err := validation.Validate(opts)
598 assert.NoError(t, err)
600 const emailAddress = "john.doe@example.com"
601 const userName = "9fcab5c9b889a557"
603 groups := []string{"a", "b"}
604 created := time.Now()
605 session := &sessions.SessionState{
606 User: userName,
607 Groups: groups,
608 Email: emailAddress,
609 AccessToken: "oauth_token",
610 CreatedAt: &created,
611 }
612 {
613 rw := httptest.NewRecorder()
614 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
615 proxy, err := NewOAuthProxy(opts, func(email string) bool {
616 return email == emailAddress
617 })
618 if err != nil {
619 t.Fatal(err)
620 }
621 proxy.addHeadersForProxying(rw, req, session)
622 assert.Equal(t, groups, req.Header["X-Forwarded-Groups"])
623 }
626func TestStripAuthHeaders(t *testing.T) {
627 testCases := map[string]struct {
628 SkipAuthStripHeaders bool
629 PassBasicAuth bool
630 PassUserHeaders bool
631 PassAccessToken bool
632 PassAuthorization bool
633 StrippedHeaders map[string]bool
634 }{
635 "Default options": {
636 SkipAuthStripHeaders: true,
637 PassBasicAuth: true,
638 PassUserHeaders: true,
639 PassAccessToken: false,
640 PassAuthorization: false,
641 StrippedHeaders: map[string]bool{
642 "X-Forwarded-User": true,
643 "X-Forwared-Groups": true,
644 "X-Forwarded-Email": true,
645 "X-Forwarded-Preferred-Username": true,
646 "X-Forwarded-Access-Token": false,
647 "Authorization": true,
648 },
649 },
650 "Pass access token": {
651 SkipAuthStripHeaders: true,
652 PassBasicAuth: true,
653 PassUserHeaders: true,
654 PassAccessToken: true,
655 PassAuthorization: false,
656 StrippedHeaders: map[string]bool{
657 "X-Forwarded-User": true,
658 "X-Forwared-Groups": true,
659 "X-Forwarded-Email": true,
660 "X-Forwarded-Preferred-Username": true,
661 "X-Forwarded-Access-Token": true,
662 "Authorization": true,
663 },
664 },
665 "Nothing setting Authorization": {
666 SkipAuthStripHeaders: true,
667 PassBasicAuth: false,
668 PassUserHeaders: true,
669 PassAccessToken: true,
670 PassAuthorization: false,
671 StrippedHeaders: map[string]bool{
672 "X-Forwarded-User": true,
673 "X-Forwared-Groups": true,
674 "X-Forwarded-Email": true,
675 "X-Forwarded-Preferred-Username": true,
676 "X-Forwarded-Access-Token": true,
677 "Authorization": false,
678 },
679 },
680 "Only Authorization header modified": {
681 SkipAuthStripHeaders: true,
682 PassBasicAuth: false,
683 PassUserHeaders: false,
684 PassAccessToken: false,
685 PassAuthorization: true,
686 StrippedHeaders: map[string]bool{
687 "X-Forwarded-User": false,
688 "X-Forwared-Groups": false,
689 "X-Forwarded-Email": false,
690 "X-Forwarded-Preferred-Username": false,
691 "X-Forwarded-Access-Token": false,
692 "Authorization": true,
693 },
694 },
695 "Don't strip any headers (default options)": {
696 SkipAuthStripHeaders: false,
697 PassBasicAuth: true,
698 PassUserHeaders: true,
699 PassAccessToken: false,
700 PassAuthorization: false,
701 StrippedHeaders: map[string]bool{
702 "X-Forwarded-User": false,
703 "X-Forwared-Groups": false,
704 "X-Forwarded-Email": false,
705 "X-Forwarded-Preferred-Username": false,
706 "X-Forwarded-Access-Token": false,
707 "Authorization": false,
708 },
709 },
710 "Don't strip any headers (custom options)": {
711 SkipAuthStripHeaders: false,
712 PassBasicAuth: true,
713 PassUserHeaders: true,
714 PassAccessToken: true,
715 PassAuthorization: false,
716 StrippedHeaders: map[string]bool{
717 "X-Forwarded-User": false,
718 "X-Forwared-Groups": false,
719 "X-Forwarded-Email": false,
720 "X-Forwarded-Preferred-Username": false,
721 "X-Forwarded-Access-Token": false,
722 "Authorization": false,
723 },
724 },
725 }
727 initialHeaders := map[string]string{
728 "X-Forwarded-User": "9fcab5c9b889a557",
729 "X-Forwarded-Email": "john.doe@example.com",
730 "X-Forwarded-Groups": "a,b,c",
731 "X-Forwarded-Preferred-Username": "john.doe",
732 "X-Forwarded-Access-Token": "AccessToken",
733 "Authorization": "bearer IDToken",
734 }
736 for name, tc := range testCases {
737 t.Run(name, func(t *testing.T) {
738 opts := baseTestOptions()
739 opts.SkipAuthStripHeaders = tc.SkipAuthStripHeaders
740 opts.PassBasicAuth = tc.PassBasicAuth
741 opts.PassUserHeaders = tc.PassUserHeaders
742 opts.PassAccessToken = tc.PassAccessToken
743 opts.PassAuthorization = tc.PassAuthorization
744 err := validation.Validate(opts)
745 assert.NoError(t, err)
747 req, _ := http.NewRequest("GET", fmt.Sprintf("%s/testCase", opts.ProxyPrefix), nil)
748 for header, val := range initialHeaders {
749 req.Header.Set(header, val)
750 }
752 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
753 assert.NoError(t, err)
754 if proxy.skipAuthStripHeaders {
755 proxy.stripAuthHeaders(req)
756 }
758 for header, stripped := range tc.StrippedHeaders {
759 if stripped {
760 assert.Equal(t, req.Header.Get(header), "")
761 } else {
762 assert.Equal(t, req.Header.Get(header), initialHeaders[header])
763 }
764 }
765 })
766 }
769type PassAccessTokenTest struct {
770 providerServer *httptest.Server
771 proxy *OAuthProxy
772 opts *options.Options
775type PassAccessTokenTestOptions struct {
776 PassAccessToken bool
777 ProxyUpstream options.Upstream
780func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTest, error) {
781 patt := &PassAccessTokenTest{}
783 patt.providerServer = httptest.NewServer(
784 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
785 var payload string
786 switch r.URL.Path {
787 case "/oauth/token":
788 payload = `{"access_token": "my_auth_token"}`
789 default:
790 payload = r.Header.Get("X-Forwarded-Access-Token")
791 if payload == "" {
792 payload = "No access token found."
793 }
794 }
795 w.WriteHeader(200)
796 _, err := w.Write([]byte(payload))
797 if err != nil {
798 panic(err)
799 }
800 }))
802 patt.opts = baseTestOptions()
803 patt.opts.UpstreamServers = options.Upstreams{
804 {
805 ID: patt.providerServer.URL,
806 Path: "/",
807 URI: patt.providerServer.URL,
808 },
809 }
810 if opts.ProxyUpstream.ID != "" {
811 patt.opts.UpstreamServers = append(patt.opts.UpstreamServers, opts.ProxyUpstream)
812 }
814 patt.opts.Cookie.Secure = false
815 patt.opts.PassAccessToken = opts.PassAccessToken
816 err := validation.Validate(patt.opts)
817 if err != nil {
818 return nil, err
819 }
821 providerURL, _ := url.Parse(patt.providerServer.URL)
822 const emailAddress = "michael.bland@gsa.gov"
824 patt.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
825 patt.proxy, err = NewOAuthProxy(patt.opts, func(email string) bool {
826 return email == emailAddress
827 })
828 if err != nil {
829 return nil, err
830 }
831 return patt, nil
834func (patTest *PassAccessTokenTest) Close() {
835 patTest.providerServer.Close()
838func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
839 cookie string) {
840 rw := httptest.NewRecorder()
841 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
842 strings.NewReader(""))
843 if err != nil {
844 return 0, ""
845 }
846 req.AddCookie(patTest.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
847 patTest.proxy.ServeHTTP(rw, req)
848 return rw.Code, rw.Header().Values("Set-Cookie")[1]
851// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
852// and cookie and returns body and status code.
853func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
854 cookieName := patTest.proxy.CookieName
855 var value string
856 keyPrefix := cookieName + "="
858 for _, field := range strings.Split(cookie, "; ") {
859 value = strings.TrimPrefix(field, keyPrefix)
860 if value != field {
861 break
862 } else {
863 value = ""
864 }
865 }
866 if value == "" {
867 return 0, ""
868 }
870 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
871 if err != nil {
872 return 0, ""
873 }
874 req.AddCookie(&http.Cookie{
875 Name: cookieName,
876 Value: value,
877 Path: "/",
878 Expires: time.Now().Add(time.Duration(24)),
879 HttpOnly: true,
880 })
882 rw := httptest.NewRecorder()
883 patTest.proxy.ServeHTTP(rw, req)
884 return rw.Code, rw.Body.String()
887func TestForwardAccessTokenUpstream(t *testing.T) {
888 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
889 PassAccessToken: true,
890 })
891 if err != nil {
892 t.Fatal(err)
893 }
894 t.Cleanup(patTest.Close)
896 // A successful validation will redirect and set the auth cookie.
897 code, cookie := patTest.getCallbackEndpoint()
898 if code != 302 {
899 t.Fatalf("expected 302; got %d", code)
900 }
901 assert.NotNil(t, cookie)
903 // Now we make a regular request; the access_token from the cookie is
904 // forwarded as the "X-Forwarded-Access-Token" header. The token is
905 // read by the test provider server and written in the response body.
906 code, payload := patTest.getEndpointWithCookie(cookie, "/")
907 if code != 200 {
908 t.Fatalf("expected 200; got %d", code)
909 }
910 assert.Equal(t, "my_auth_token", payload)
913func TestStaticProxyUpstream(t *testing.T) {
914 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
915 PassAccessToken: true,
916 ProxyUpstream: options.Upstream{
917 ID: "static-proxy",
918 Path: "/static-proxy",
919 Static: true,
920 },
921 })
922 if err != nil {
923 t.Fatal(err)
924 }
925 t.Cleanup(patTest.Close)
927 // A successful validation will redirect and set the auth cookie.
928 code, cookie := patTest.getCallbackEndpoint()
929 if code != 302 {
930 t.Fatalf("expected 302; got %d", code)
931 }
932 assert.NotEqual(t, nil, cookie)
934 // Now we make a regular request against the upstream proxy; And validate
935 // the returned status code through the static proxy.
936 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
937 if code != 200 {
938 t.Fatalf("expected 200; got %d", code)
939 }
940 assert.Equal(t, "Authenticated", payload)
943func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
944 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
945 PassAccessToken: false,
946 })
947 if err != nil {
948 t.Fatal(err)
949 }
950 t.Cleanup(patTest.Close)
952 // A successful validation will redirect and set the auth cookie.
953 code, cookie := patTest.getCallbackEndpoint()
954 if code != 302 {
955 t.Fatalf("expected 302; got %d", code)
956 }
957 assert.NotEqual(t, nil, cookie)
959 // Now we make a regular request, but the access token header should
960 // not be present.
961 code, payload := patTest.getEndpointWithCookie(cookie, "/")
962 if code != 200 {
963 t.Fatalf("expected 200; got %d", code)
964 }
965 assert.Equal(t, "No access token found.", payload)
968type SignInPageTest struct {
969 opts *options.Options
970 proxy *OAuthProxy
971 signInRegexp *regexp.Regexp
972 signInProviderRegexp *regexp.Regexp
975const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
976const signInSkipProvider = `>Found<`
978func NewSignInPageTest(skipProvider bool) (*SignInPageTest, error) {
979 var sipTest SignInPageTest
981 sipTest.opts = baseTestOptions()
982 sipTest.opts.SkipProviderButton = skipProvider
983 err := validation.Validate(sipTest.opts)
984 if err != nil {
985 return nil, err
986 }
988 sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
989 return true
990 })
991 if err != nil {
992 return nil, err
993 }
994 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
995 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
997 return &sipTest, nil
1000func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
1001 rw := httptest.NewRecorder()
1002 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
1003 sipTest.proxy.ServeHTTP(rw, req)
1004 return rw.Code, rw.Body.String()
1007func TestSignInPageIncludesTargetRedirect(t *testing.T) {
1008 sipTest, err := NewSignInPageTest(false)
1009 if err != nil {
1010 t.Fatal(err)
1012 const endpoint = "/some/random/endpoint"
1014 code, body := sipTest.GetEndpoint(endpoint)
1015 assert.Equal(t, 403, code)
1017 match := sipTest.signInRegexp.FindStringSubmatch(body)
1018 if match == nil {
1019 t.Fatal("Did not find pattern in body: " +
1020 signInRedirectPattern + "\nBody:\n" + body)
1022 if match[1] != endpoint {
1023 t.Fatal(`expected redirect to "` + endpoint +
1024 `", but was "` + match[1] + `"`)
1028func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
1029 sipTest, err := NewSignInPageTest(false)
1030 if err != nil {
1031 t.Fatal(err)
1033 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
1034 assert.Equal(t, 200, code)
1036 match := sipTest.signInRegexp.FindStringSubmatch(body)
1037 if match == nil {
1038 t.Fatal("Did not find pattern in body: " +
1039 signInRedirectPattern + "\nBody:\n" + body)
1041 if match[1] != "/" {
1042 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
1046func TestSignInPageSkipProvider(t *testing.T) {
1047 sipTest, err := NewSignInPageTest(true)
1048 if err != nil {
1049 t.Fatal(err)
1052 endpoint := "/some/random/endpoint"
1054 code, body := sipTest.GetEndpoint(endpoint)
1055 assert.Equal(t, 302, code)
1057 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
1058 if match == nil {
1059 t.Fatal("Did not find pattern in body: " +
1060 signInSkipProvider + "\nBody:\n" + body)
1064func TestSignInPageSkipProviderDirect(t *testing.T) {
1065 sipTest, err := NewSignInPageTest(true)
1066 if err != nil {
1067 t.Fatal(err)
1070 endpoint := "/sign_in"
1072 code, body := sipTest.GetEndpoint(endpoint)
1073 assert.Equal(t, 302, code)
1075 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
1076 if match == nil {
1077 t.Fatal("Did not find pattern in body: " +
1078 signInSkipProvider + "\nBody:\n" + body)
1082type ProcessCookieTest struct {
1083 opts *options.Options
1084 proxy *OAuthProxy
1085 rw *httptest.ResponseRecorder
1086 req *http.Request
1087 validateUser bool
1090type ProcessCookieTestOpts struct {
1091 providerValidateCookieResponse bool
1094type OptionsModifier func(*options.Options)
1096func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1097 var pcTest ProcessCookieTest
1099 pcTest.opts = baseTestOptions()
1100 for _, modifier := range modifiers {
1101 modifier(pcTest.opts)
1103 // First, set the CookieRefresh option so proxy.AesCipher is created,
1104 // needed to encrypt the access_token.
1105 pcTest.opts.Cookie.Refresh = time.Hour
1106 err := validation.Validate(pcTest.opts)
1107 if err != nil {
1108 return nil, err
1111 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1112 return pcTest.validateUser
1113 })
1114 if err != nil {
1115 return nil, err
1117 pcTest.proxy.provider = &TestProvider{
1118 ValidToken: opts.providerValidateCookieResponse,
1121 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
1122 // access_token validation.
1123 pcTest.proxy.CookieRefresh = time.Duration(0)
1124 pcTest.rw = httptest.NewRecorder()
1125 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
1126 pcTest.validateUser = true
1127 return &pcTest, nil
1130func NewProcessCookieTestWithDefaults() (*ProcessCookieTest, error) {
1131 return NewProcessCookieTest(ProcessCookieTestOpts{
1132 providerValidateCookieResponse: true,
1133 })
1136func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1137 return NewProcessCookieTest(ProcessCookieTestOpts{
1138 providerValidateCookieResponse: true,
1139 }, modifiers...)
1142func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
1143 err := p.proxy.SaveSession(p.rw, p.req, s)
1144 if err != nil {
1145 return err
1147 for _, cookie := range p.rw.Result().Cookies() {
1148 p.req.AddCookie(cookie)
1150 return nil
1153func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
1154 return p.proxy.LoadCookiedSession(p.req)
1157func TestLoadCookiedSession(t *testing.T) {
1158 pcTest, err := NewProcessCookieTestWithDefaults()
1159 if err != nil {
1160 t.Fatal(err)
1163 created := time.Now()
1164 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
1165 err = pcTest.SaveSession(startSession)
1166 assert.NoError(t, err)
1168 session, err := pcTest.LoadCookiedSession()
1169 if err != nil {
1170 t.Fatal(err)
1172 assert.Equal(t, startSession.Email, session.Email)
1173 assert.Equal(t, "", session.User)
1174 assert.Equal(t, startSession.AccessToken, session.AccessToken)
1177func TestProcessCookieNoCookieError(t *testing.T) {
1178 pcTest, err := NewProcessCookieTestWithDefaults()
1179 if err != nil {
1180 t.Fatal(err)
1183 session, err := pcTest.LoadCookiedSession()
1184 assert.Error(t, err, "cookie \"_oauth2_proxy\" not present")
1185 if session != nil {
1186 t.Errorf("expected nil session. got %#v", session)
1190func TestProcessCookieRefreshNotSet(t *testing.T) {
1191 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1192 opts.Cookie.Expire = time.Duration(23) * time.Hour
1193 })
1194 if err != nil {
1195 t.Fatal(err)
1198 reference := time.Now().Add(time.Duration(-2) * time.Hour)
1200 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1201 err = pcTest.SaveSession(startSession)
1202 assert.NoError(t, err)
1204 session, err := pcTest.LoadCookiedSession()
1205 assert.Equal(t, nil, err)
1206 if session.Age() < time.Duration(-2)*time.Hour {
1207 t.Errorf("cookie too young %v", session.Age())
1209 assert.Equal(t, startSession.Email, session.Email)
1212func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1213 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1214 opts.Cookie.Expire = time.Duration(24) * time.Hour
1215 })
1216 if err != nil {
1217 t.Fatal(err)
1220 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1221 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1222 err = pcTest.SaveSession(startSession)
1223 assert.NoError(t, err)
1225 session, err := pcTest.LoadCookiedSession()
1226 assert.NotEqual(t, nil, err)
1227 if session != nil {
1228 t.Errorf("expected nil session %#v", session)
1232func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1233 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1234 opts.Cookie.Expire = time.Duration(24) * time.Hour
1235 })
1236 if err != nil {
1237 t.Fatal(err)
1240 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1241 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1242 err = pcTest.SaveSession(startSession)
1243 assert.NoError(t, err)
1245 pcTest.proxy.CookieRefresh = time.Hour
1246 session, err := pcTest.LoadCookiedSession()
1247 assert.NotEqual(t, nil, err)
1248 if session != nil {
1249 t.Errorf("expected nil session %#v", session)
1253func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
1254 pcTest, err := NewProcessCookieTestWithDefaults()
1255 if err != nil {
1256 return nil, err
1258 pcTest.req, _ = http.NewRequest("GET",
1259 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1260 return pcTest, nil
1263func TestUserInfoEndpointAccepted(t *testing.T) {
1264 test, err := NewUserInfoEndpointTest()
1265 if err != nil {
1266 t.Fatal(err)
1269 startSession := &sessions.SessionState{
1270 Email: "john.doe@example.com", AccessToken: "my_access_token"}
1271 err = test.SaveSession(startSession)
1272 assert.NoError(t, err)
1274 test.proxy.ServeHTTP(test.rw, test.req)
1275 assert.Equal(t, http.StatusOK, test.rw.Code)
1276 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1277 assert.Equal(t, "{\"email\":\"john.doe@example.com\"}\n", string(bodyBytes))
1280func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1281 test, err := NewUserInfoEndpointTest()
1282 if err != nil {
1283 t.Fatal(err)
1286 test.proxy.ServeHTTP(test.rw, test.req)
1287 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1290func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1291 pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1292 if err != nil {
1293 return nil, err
1295 pcTest.req, _ = http.NewRequest("GET",
1296 pcTest.opts.ProxyPrefix+"/auth", nil)
1297 return pcTest, nil
1300func TestAuthOnlyEndpointAccepted(t *testing.T) {
1301 test, err := NewAuthOnlyEndpointTest()
1302 if err != nil {
1303 t.Fatal(err)
1306 created := time.Now()
1307 startSession := &sessions.SessionState{
1308 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1309 err = test.SaveSession(startSession)
1310 assert.NoError(t, err)
1312 test.proxy.ServeHTTP(test.rw, test.req)
1313 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1314 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1315 assert.Equal(t, "", string(bodyBytes))
1318func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1319 test, err := NewAuthOnlyEndpointTest()
1320 if err != nil {
1321 t.Fatal(err)
1324 test.proxy.ServeHTTP(test.rw, test.req)
1325 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1326 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1327 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1330func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1331 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1332 opts.Cookie.Expire = time.Duration(24) * time.Hour
1333 })
1334 if err != nil {
1335 t.Fatal(err)
1338 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1339 startSession := &sessions.SessionState{
1340 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1341 err = test.SaveSession(startSession)
1342 assert.NoError(t, err)
1344 test.proxy.ServeHTTP(test.rw, test.req)
1345 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1346 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1347 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1350func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1351 test, err := NewAuthOnlyEndpointTest()
1352 if err != nil {
1353 t.Fatal(err)
1356 created := time.Now()
1357 startSession := &sessions.SessionState{
1358 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1359 err = test.SaveSession(startSession)
1360 assert.NoError(t, err)
1361 test.validateUser = false
1363 test.proxy.ServeHTTP(test.rw, test.req)
1364 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1365 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1366 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1369func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1370 var pcTest ProcessCookieTest
1372 pcTest.opts = baseTestOptions()
1373 pcTest.opts.SetXAuthRequest = true
1374 pcTest.opts.AllowedGroups = []string{"oauth_groups"}
1375 err := validation.Validate(pcTest.opts)
1376 assert.NoError(t, err)
1378 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1379 return pcTest.validateUser
1380 })
1381 if err != nil {
1382 t.Fatal(err)
1384 pcTest.proxy.provider = &TestProvider{
1385 ValidToken: true,
1388 pcTest.validateUser = true
1390 pcTest.rw = httptest.NewRecorder()
1391 pcTest.req, _ = http.NewRequest("GET",
1392 pcTest.opts.ProxyPrefix+"/auth", nil)
1394 created := time.Now()
1395 startSession := &sessions.SessionState{
1396 User: "oauth_user", Groups: []string{"oauth_groups"}, Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1397 err = pcTest.SaveSession(startSession)
1398 assert.NoError(t, err)
1400 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1401 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1402 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1403 assert.Equal(t, startSession.Groups, pcTest.rw.Header().Values("X-Auth-Request-Groups"))
1404 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1407func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1408 var pcTest ProcessCookieTest
1410 pcTest.opts = baseTestOptions()
1411 pcTest.opts.SetXAuthRequest = true
1412 pcTest.opts.SetBasicAuth = true
1413 err := validation.Validate(pcTest.opts)
1414 assert.NoError(t, err)
1416 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1417 return pcTest.validateUser
1418 })
1419 if err != nil {
1420 t.Fatal(err)
1422 pcTest.proxy.provider = &TestProvider{
1423 ValidToken: true,
1426 pcTest.validateUser = true
1428 pcTest.rw = httptest.NewRecorder()
1429 pcTest.req, _ = http.NewRequest("GET",
1430 pcTest.opts.ProxyPrefix+"/auth", nil)
1432 created := time.Now()
1433 startSession := &sessions.SessionState{
1434 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1435 err = pcTest.SaveSession(startSession)
1436 assert.NoError(t, err)
1438 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1439 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1440 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1441 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1442 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:"+pcTest.opts.BasicAuthPassword))
1443 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1446func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1447 var pcTest ProcessCookieTest
1449 pcTest.opts = baseTestOptions()
1450 pcTest.opts.SetXAuthRequest = true
1451 pcTest.opts.SetBasicAuth = false
1452 err := validation.Validate(pcTest.opts)
1453 assert.NoError(t, err)
1455 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1456 return pcTest.validateUser
1457 })
1458 if err != nil {
1459 t.Fatal(err)
1461 pcTest.proxy.provider = &TestProvider{
1462 ValidToken: true,
1465 pcTest.validateUser = true
1467 pcTest.rw = httptest.NewRecorder()
1468 pcTest.req, _ = http.NewRequest("GET",
1469 pcTest.opts.ProxyPrefix+"/auth", nil)
1471 created := time.Now()
1472 startSession := &sessions.SessionState{
1473 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1474 err = pcTest.SaveSession(startSession)
1475 assert.NoError(t, err)
1477 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1478 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1479 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1480 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1481 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1484func TestAuthSkippedForPreflightRequests(t *testing.T) {
1485 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1486 w.WriteHeader(200)
1487 _, err := w.Write([]byte("response"))
1488 if err != nil {
1489 t.Fatal(err)
1491 }))
1492 t.Cleanup(upstream.Close)
1494 opts := baseTestOptions()
1495 opts.UpstreamServers = options.Upstreams{
1497 ID: upstream.URL,
1498 Path: "/",
1499 URI: upstream.URL,
1500 },
1502 opts.SkipAuthPreflight = true
1503 err := validation.Validate(opts)
1504 assert.NoError(t, err)
1506 upstreamURL, _ := url.Parse(upstream.URL)
1507 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1509 proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
1510 if err != nil {
1511 t.Fatal(err)
1513 rw := httptest.NewRecorder()
1514 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1515 proxy.ServeHTTP(rw, req)
1517 assert.Equal(t, 200, rw.Code)
1518 assert.Equal(t, "response", rw.Body.String())
1521type SignatureAuthenticator struct {
1522 auth hmacauth.HmacAuth
1525func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1526 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1528 var msg string
1529 switch result {
1530 case hmacauth.ResultNoSignature:
1531 msg = "no signature received"
1532 case hmacauth.ResultMatch:
1533 msg = "signatures match"
1534 case hmacauth.ResultMismatch:
1535 msg = fmt.Sprintf(
1536 "signatures do not match:\n received: %s\n computed: %s",
1537 headerSig,
1538 computedSig)
1539 default:
1540 panic("unknown result value: " + result.String())
1543 _, err := w.Write([]byte(msg))
1544 if err != nil {
1545 panic(err)
1549type SignatureTest struct {
1550 opts *options.Options
1551 upstream *httptest.Server
1552 upstreamHost string
1553 provider *httptest.Server
1554 header http.Header
1555 rw *httptest.ResponseRecorder
1556 authenticator *SignatureAuthenticator
1559func NewSignatureTest() (*SignatureTest, error) {
1560 opts := baseTestOptions()
1561 opts.EmailDomains = []string{"acm.org"}
1563 authenticator := &SignatureAuthenticator{}
1564 upstream := httptest.NewServer(
1565 http.HandlerFunc(authenticator.Authenticate))
1566 upstreamURL, err := url.Parse(upstream.URL)
1567 if err != nil {
1568 return nil, err
1570 opts.UpstreamServers = options.Upstreams{
1572 ID: upstream.URL,
1573 Path: "/",
1574 URI: upstream.URL,
1575 },
1578 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1579 _, err := w.Write([]byte(`{"access_token": "my_auth_token"}`))
1580 if err != nil {
1581 panic(err)
1584 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1585 providerURL, err := url.Parse(provider.URL)
1586 if err != nil {
1587 return nil, err
1589 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1591 return &SignatureTest{
1592 opts,
1593 upstream,
1594 upstreamURL.Host,
1595 provider,
1596 make(http.Header),
1597 httptest.NewRecorder(),
1598 authenticator,
1599 }, nil
1602func (st *SignatureTest) Close() {
1603 st.provider.Close()
1604 st.upstream.Close()
1607// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1608// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1609// https://github.com/18F/hmacauth/pull/4
1610type fakeNetConn struct {
1611 reqBody string
1614func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1615 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1616 copy(p, fnc.reqBody)
1617 fnc.reqBody = ""
1618 return bodyLen, io.EOF
1620 return 0, io.EOF
1623func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) error {
1624 err := validation.Validate(st.opts)
1625 if err != nil {
1626 return err
1628 proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
1629 if err != nil {
1630 return err
1633 var bodyBuf io.ReadCloser
1634 if body != "" {
1635 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1637 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1638 req.Header = st.header
1640 state := &sessions.SessionState{
1641 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1642 err = proxy.SaveSession(st.rw, req, state)
1643 if err != nil {
1644 return err
1646 for _, c := range st.rw.Result().Cookies() {
1647 req.AddCookie(c)
1649 // This is used by the upstream to validate the signature.
1650 st.authenticator.auth = hmacauth.NewHmacAuth(
1651 crypto.SHA1, []byte(key), upstream.SignatureHeader, upstream.SignatureHeaders)
1652 proxy.ServeHTTP(st.rw, req)
1654 return nil
1657func TestRequestSignature(t *testing.T) {
1658 testCases := map[string]struct {
1659 method string
1660 body string
1661 key string
1662 resp string
1663 }{
1664 "No request signature": {
1665 method: "GET",
1666 body: "",
1667 key: "",
1668 resp: "no signature received",
1669 },
1670 "Get request": {
1671 method: "GET",
1672 body: "",
1673 key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1674 resp: "signatures match",
1675 },
1676 "Post request": {
1677 method: "POST",
1678 body: `{ "hello": "world!" }`,
1679 key: "d90df39e2d19282840252612dd7c81421a372f61",
1680 resp: "signatures match",
1681 },
1683 for name, tc := range testCases {
1684 t.Run(name, func(t *testing.T) {
1685 st, err := NewSignatureTest()
1686 if err != nil {
1687 t.Fatal(err)
1689 t.Cleanup(st.Close)
1690 if tc.key != "" {
1691 st.opts.SignatureKey = fmt.Sprintf("sha1:%s", tc.key)
1693 err = st.MakeRequestWithExpectedKey(tc.method, tc.body, tc.key)
1694 assert.NoError(t, err)
1695 assert.Equal(t, 200, st.rw.Code)
1696 assert.Equal(t, tc.resp, st.rw.Body.String())
1697 })
1701func TestGetRedirect(t *testing.T) {
1702 opts := baseTestOptions()
1703 err := validation.Validate(opts)
1704 assert.NoError(t, err)
1705 require.NotEmpty(t, opts.ProxyPrefix)
1706 proxy, err := NewOAuthProxy(opts, func(s string) bool { return false })
1707 if err != nil {
1708 t.Fatal(err)
1711 tests := []struct {
1712 name string
1713 url string
1714 expectedRedirect string
1715 }{
1717 name: "request outside of ProxyPrefix redirects to original URL",
1718 url: "/foo/bar",
1719 expectedRedirect: "/foo/bar",
1720 },
1722 name: "request with query preserves query",
1723 url: "/foo?bar",
1724 expectedRedirect: "/foo?bar",
1725 },
1727 name: "request under ProxyPrefix redirects to root",
1728 url: proxy.ProxyPrefix + "/foo/bar",
1729 expectedRedirect: "/",
1730 },
1732 for _, tt := range tests {
1733 t.Run(tt.name, func(t *testing.T) {
1734 req, _ := http.NewRequest("GET", tt.url, nil)
1735 redirect, err := proxy.GetRedirect(req)
1737 assert.NoError(t, err)
1738 assert.Equal(t, tt.expectedRedirect, redirect)
1739 })
1743type ajaxRequestTest struct {
1744 opts *options.Options
1745 proxy *OAuthProxy
1748func newAjaxRequestTest() (*ajaxRequestTest, error) {
1749 test := &ajaxRequestTest{}
1750 test.opts = baseTestOptions()
1751 err := validation.Validate(test.opts)
1752 if err != nil {
1753 return nil, err
1756 test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
1757 return true
1758 })
1759 if err != nil {
1760 return nil, err
1762 return test, nil
1765func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1766 rw := httptest.NewRecorder()
1767 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1768 if err != nil {
1769 return 0, nil, err
1771 req.Header = header
1772 test.proxy.ServeHTTP(rw, req)
1773 return rw.Code, rw.Header(), nil
1776func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1777 test, err := newAjaxRequestTest()
1778 if err != nil {
1779 t.Fatal(err)
1781 endpoint := "/test"
1783 code, rh, err := test.getEndpoint(endpoint, header)
1784 assert.NoError(t, err)
1785 assert.Equal(t, http.StatusUnauthorized, code)
1786 mime := rh.Get("Content-Type")
1787 assert.Equal(t, applicationJSON, mime)
1789func TestAjaxUnauthorizedRequest1(t *testing.T) {
1790 header := make(http.Header)
1791 header.Add("accept", applicationJSON)
1793 testAjaxUnauthorizedRequest(t, header)
1796func TestAjaxUnauthorizedRequest2(t *testing.T) {
1797 header := make(http.Header)
1798 header.Add("Accept", applicationJSON)
1800 testAjaxUnauthorizedRequest(t, header)
1803func TestAjaxForbiddendRequest(t *testing.T) {
1804 test, err := newAjaxRequestTest()
1805 if err != nil {
1806 t.Fatal(err)
1808 endpoint := "/test"
1809 header := make(http.Header)
1810 code, rh, err := test.getEndpoint(endpoint, header)
1811 assert.NoError(t, err)
1812 assert.Equal(t, http.StatusForbidden, code)
1813 mime := rh.Get("Content-Type")
1814 assert.NotEqual(t, applicationJSON, mime)
1817func TestClearSplitCookie(t *testing.T) {
1818 opts := baseTestOptions()
1819 opts.Cookie.Secret = base64CookieSecret
1820 opts.Cookie.Name = "oauth2"
1821 opts.Cookie.Domains = []string{"abc"}
1822 err := validation.Validate(opts)
1823 assert.NoError(t, err)
1825 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1826 if err != nil {
1827 t.Fatal(err)
1830 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1831 var rw = httptest.NewRecorder()
1832 req := httptest.NewRequest("get", "/", nil)
1834 req.AddCookie(&http.Cookie{
1835 Name: "test1",
1836 Value: "test1",
1837 })
1838 req.AddCookie(&http.Cookie{
1839 Name: "oauth2_0",
1840 Value: "oauth2_0",
1841 })
1842 req.AddCookie(&http.Cookie{
1843 Name: "oauth2_1",
1844 Value: "oauth2_1",
1845 })
1847 err = p.ClearSessionCookie(rw, req)
1848 assert.NoError(t, err)
1849 header := rw.Header()
1851 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1854func TestClearSingleCookie(t *testing.T) {
1855 opts := baseTestOptions()
1856 opts.Cookie.Name = "oauth2"
1857 opts.Cookie.Domains = []string{"abc"}
1858 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1859 if err != nil {
1860 t.Fatal(err)
1863 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1864 var rw = httptest.NewRecorder()
1865 req := httptest.NewRequest("get", "/", nil)
1867 req.AddCookie(&http.Cookie{
1868 Name: "test1",
1869 Value: "test1",
1870 })
1871 req.AddCookie(&http.Cookie{
1872 Name: "oauth2",
1873 Value: "oauth2",
1874 })
1876 err = p.ClearSessionCookie(rw, req)
1877 assert.NoError(t, err)
1878 header := rw.Header()
1880 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1883type NoOpKeySet struct {
1886func (NoOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
1887 splitStrings := strings.Split(jwt, ".")
1888 payloadString := splitStrings[1]
1889 return base64.RawURLEncoding.DecodeString(payloadString)
1892func TestGetJwtSession(t *testing.T) {
1893 /* token payload:
1895 "sub": "1234567890",
1896 "aud": "https://test.myapp.com",
1897 "name": "John Doe",
1898 "email": "john@example.com",
1899 "iss": "https://issuer.example.com",
1900 "iat": 1553691215,
1901 "exp": 1912151821
1903 */
1904 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1905 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1906 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1907 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1908 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1909 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1911 keyset := NoOpKeySet{}
1912 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1913 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1915 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1916 opts.PassAuthorization = true
1917 opts.SetAuthorization = true
1918 opts.SetXAuthRequest = true
1919 opts.SkipJwtBearerTokens = true
1920 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
1921 })
1922 if err != nil {
1923 t.Fatal(err)
1925 tp, _ := test.proxy.provider.(*TestProvider)
1926 tp.GroupValidator = func(s string) bool {
1927 return true
1930 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1931 test.req.Header = map[string][]string{
1932 "Authorization": {authHeader},
1935 // Bearer
1936 expires := time.Unix(1912151821, 0)
1937 session, err := test.proxy.getAuthenticatedSession(test.rw, test.req)
1938 assert.NoError(t, err)
1939 assert.Equal(t, session.User, "1234567890")
1940 assert.Equal(t, session.Email, "john@example.com")
1941 assert.Equal(t, session.ExpiresOn, &expires)
1942 assert.Equal(t, session.IDToken, goodJwt)
1944 test.proxy.ServeHTTP(test.rw, test.req)
1945 if test.rw.Code >= 400 {
1946 t.Fatalf("expected 3xx got %d", test.rw.Code)
1949 // Check PassAuthorization, should overwrite Basic header
1950 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
1951 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "1234567890")
1952 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
1954 // SetAuthorization and SetXAuthRequest
1955 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
1956 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "1234567890")
1957 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
1960func Test_prepareNoCache(t *testing.T) {
1961 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1962 prepareNoCache(w)
1963 })
1964 mux := http.NewServeMux()
1965 mux.Handle("/", handler)
1967 rec := httptest.NewRecorder()
1968 req := httptest.NewRequest(http.MethodGet, "/", nil)
1969 mux.ServeHTTP(rec, req)
1971 for k, v := range noCacheHeaders {
1972 assert.Equal(t, rec.Header().Get(k), v)
1976func Test_noCacheHeaders(t *testing.T) {
1977 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1978 _, err := w.Write([]byte("upstream"))
1979 if err != nil {
1980 t.Error(err)
1982 }))
1983 t.Cleanup(upstream.Close)
1985 opts := baseTestOptions()
1986 opts.UpstreamServers = options.Upstreams{
1988 ID: upstream.URL,
1989 Path: "/",
1990 URI: upstream.URL,
1991 },
1993 opts.SkipAuthRegex = []string{".*"}
1994 err := validation.Validate(opts)
1995 assert.NoError(t, err)
1996 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
1997 if err != nil {
1998 t.Fatal(err)
2001 t.Run("not exist in response from upstream", func(t *testing.T) {
2002 rec := httptest.NewRecorder()
2003 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
2004 proxy.ServeHTTP(rec, req)
2006 assert.Equal(t, http.StatusOK, rec.Code)
2007 assert.Equal(t, "upstream", rec.Body.String())
2009 // checking noCacheHeaders does not exists in response headers from upstream
2010 for k := range noCacheHeaders {
2011 assert.Equal(t, "", rec.Header().Get(k))
2013 })
2015 t.Run("has no-cache", func(t *testing.T) {
2016 tests := []struct {
2017 path string
2018 hasNoCache bool
2019 }{
2021 path: "/oauth2/sign_in",
2022 hasNoCache: true,
2023 },
2025 path: "/oauth2/sign_out",
2026 hasNoCache: true,
2027 },
2029 path: "/oauth2/start",
2030 hasNoCache: true,
2031 },
2033 path: "/oauth2/callback",
2034 hasNoCache: true,
2035 },
2037 path: "/oauth2/auth",
2038 hasNoCache: false,
2039 },
2041 path: "/oauth2/userinfo",
2042 hasNoCache: true,
2043 },
2045 path: "/upstream",
2046 hasNoCache: false,
2047 },
2050 for _, tt := range tests {
2051 t.Run(tt.path, func(t *testing.T) {
2052 rec := httptest.NewRecorder()
2053 req := httptest.NewRequest(http.MethodGet, tt.path, nil)
2054 proxy.ServeHTTP(rec, req)
2055 cacheControl := rec.Result().Header.Get("Cache-Control")
2056 if tt.hasNoCache != (strings.Contains(cacheControl, "no-cache")) {
2057 t.Errorf(`unexpected "Cache-Control" header: %s`, cacheControl)
2059 })
2062 })
2065func baseTestOptions() *options.Options {
2066 opts := options.NewOptions()
2067 opts.Cookie.Secret = rawCookieSecret
2068 opts.ClientID = clientID
2069 opts.ClientSecret = clientSecret
2070 opts.EmailDomains = []string{"*"}
2071 return opts
2074func TestTrustedIPs(t *testing.T) {
2075 tests := []struct {
2076 name string
2077 trustedIPs []string
2078 reverseProxy bool
2079 realClientIPHeader string
2080 req *http.Request
2081 expectTrusted bool
2082 }{
2083 // Check unconfigured behavior.
2085 name: "Default",
2086 trustedIPs: nil,
2087 reverseProxy: false,
2088 realClientIPHeader: "X-Real-IP", // Default value
2089 req: func() *http.Request {
2090 req, _ := http.NewRequest("GET", "/", nil)
2091 return req
2092 }(),
2093 expectTrusted: false,
2094 },
2095 // Check using req.RemoteAddr (Options.ReverseProxy == false).
2097 name: "WithRemoteAddr",
2098 trustedIPs: []string{"127.0.0.1"},
2099 reverseProxy: false,
2100 realClientIPHeader: "X-Real-IP", // Default value
2101 req: func() *http.Request {
2102 req, _ := http.NewRequest("GET", "/", nil)
2103 req.RemoteAddr = "127.0.0.1:43670"
2104 return req
2105 }(),
2106 expectTrusted: true,
2107 },
2108 // Check ignores req.RemoteAddr match when behind a reverse proxy / missing header.
2110 name: "IgnoresRemoteAddrInReverseProxyMode",
2111 trustedIPs: []string{"127.0.0.1"},
2112 reverseProxy: true,
2113 realClientIPHeader: "X-Real-IP", // Default value
2114 req: func() *http.Request {
2115 req, _ := http.NewRequest("GET", "/", nil)
2116 req.RemoteAddr = "127.0.0.1:44324"
2117 return req
2118 }(),
2119 expectTrusted: false,
2120 },
2121 // Check successful trusting of localhost in IPv4.
2123 name: "TrustsLocalhostInReverseProxyMode",
2124 trustedIPs: []string{"127.0.0.0/8", "::1"},
2125 reverseProxy: true,
2126 realClientIPHeader: "X-Forwarded-For",
2127 req: func() *http.Request {
2128 req, _ := http.NewRequest("GET", "/", nil)
2129 req.Header.Add("X-Forwarded-For", "127.0.0.1")
2130 return req
2131 }(),
2132 expectTrusted: true,
2133 },
2134 // Check successful trusting of localhost in IPv6.
2136 name: "TrustsIP6LocalostInReverseProxyMode",
2137 trustedIPs: []string{"127.0.0.0/8", "::1"},
2138 reverseProxy: true,
2139 realClientIPHeader: "X-Forwarded-For",
2140 req: func() *http.Request {
2141 req, _ := http.NewRequest("GET", "/", nil)
2142 req.Header.Add("X-Forwarded-For", "::1")
2143 return req
2144 }(),
2145 expectTrusted: true,
2146 },
2147 // Check does not trust random IPv4 address.
2149 name: "DoesNotTrustRandomIP4Address",
2150 trustedIPs: []string{"127.0.0.0/8", "::1"},
2151 reverseProxy: true,
2152 realClientIPHeader: "X-Forwarded-For",
2153 req: func() *http.Request {
2154 req, _ := http.NewRequest("GET", "/", nil)
2155 req.Header.Add("X-Forwarded-For", "12.34.56.78")
2156 return req
2157 }(),
2158 expectTrusted: false,
2159 },
2160 // Check does not trust random IPv6 address.
2162 name: "DoesNotTrustRandomIP6Address",
2163 trustedIPs: []string{"127.0.0.0/8", "::1"},
2164 reverseProxy: true,
2165 realClientIPHeader: "X-Forwarded-For",
2166 req: func() *http.Request {
2167 req, _ := http.NewRequest("GET", "/", nil)
2168 req.Header.Add("X-Forwarded-For", "::2")
2169 return req
2170 }(),
2171 expectTrusted: false,
2172 },
2173 // Check respects correct header.
2175 name: "RespectsCorrectHeaderInReverseProxyMode",
2176 trustedIPs: []string{"127.0.0.0/8", "::1"},
2177 reverseProxy: true,
2178 realClientIPHeader: "X-Forwarded-For",
2179 req: func() *http.Request {
2180 req, _ := http.NewRequest("GET", "/", nil)
2181 req.Header.Add("X-Real-IP", "::1")
2182 return req
2183 }(),
2184 expectTrusted: false,
2185 },
2186 // Check doesn't trust if garbage is provided.
2188 name: "DoesNotTrustGarbageInReverseProxyMode",
2189 trustedIPs: []string{"127.0.0.0/8", "::1"},
2190 reverseProxy: true,
2191 realClientIPHeader: "X-Forwarded-For",
2192 req: func() *http.Request {
2193 req, _ := http.NewRequest("GET", "/", nil)
2194 req.Header.Add("X-Forwarded-For", "adsfljk29242as!!")
2195 return req
2196 }(),
2197 expectTrusted: false,
2198 },
2199 // Check doesn't trust if garbage is provided (no reverse-proxy).
2201 name: "DoesNotTrustGarbage",
2202 trustedIPs: []string{"127.0.0.0/8", "::1"},
2203 reverseProxy: false,
2204 realClientIPHeader: "X-Real-IP",
2205 req: func() *http.Request {
2206 req, _ := http.NewRequest("GET", "/", nil)
2207 req.RemoteAddr = "adsfljk29242as!!"
2208 return req
2209 }(),
2210 expectTrusted: false,
2211 },
2214 for _, tt := range tests {
2215 t.Run(tt.name, func(t *testing.T) {
2216 opts := baseTestOptions()
2217 opts.UpstreamServers = options.Upstreams{
2219 ID: "static",
2220 Path: "/",
2221 Static: true,
2222 },
2224 opts.TrustedIPs = tt.trustedIPs
2225 opts.ReverseProxy = tt.reverseProxy
2226 opts.RealClientIPHeader = tt.realClientIPHeader
2227 validation.Validate(opts)
2229 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
2230 assert.NoError(t, err)
2231 rw := httptest.NewRecorder()
2233 proxy.ServeHTTP(rw, tt.req)
2234 if tt.expectTrusted {
2235 assert.Equal(t, 200, rw.Code)
2236 } else {
2237 assert.Equal(t, 403, rw.Code)
2239 })
2243func TestProxyAllowedGroups(t *testing.T) {
2244 tests := []struct {
2245 name string
2246 allowedGroups []string
2247 groups []string
2248 expectUnauthorized bool
2249 }{
2250 {"NoAllowedGroups", []string{}, []string{}, false},
2251 {"NoAllowedGroupsUserHasGroups", []string{}, []string{"a", "b"}, false},
2252 {"UserInAllowedGroup", []string{"a"}, []string{"a", "b"}, false},
2253 {"UserNotInAllowedGroup", []string{"a"}, []string{"c"}, true},
2256 for _, tt := range tests {
2257 t.Run(tt.name, func(t *testing.T) {
2258 emailAddress := "test"
2259 created := time.Now()
2261 session := &sessions.SessionState{
2262 Groups: tt.groups,
2263 Email: emailAddress,
2264 AccessToken: "oauth_token",
2265 CreatedAt: &created,
2268 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2269 w.WriteHeader(200)
2270 }))
2271 t.Cleanup(upstream.Close)
2273 test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
2274 opts.AllowedGroups = tt.allowedGroups
2275 opts.UpstreamServers = options.Upstreams{
2277 ID: upstream.URL,
2278 Path: "/",
2279 URI: upstream.URL,
2280 },
2282 })
2283 if err != nil {
2284 t.Fatal(err)
2287 test.req, _ = http.NewRequest("GET", "/", nil)
2289 test.req.Header.Add("accept", applicationJSON)
2290 test.SaveSession(session)
2291 test.proxy.ServeHTTP(test.rw, test.req)
2293 if tt.expectUnauthorized {
2294 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
2295 } else {
2296 assert.Equal(t, http.StatusOK, test.rw.Code)
2298 })
2302func TestAuthOnlyAllowedGroups(t *testing.T) {
2303 tests := []struct {
2304 name string
2305 allowedGroups []string
2306 groups []string
2307 expectUnauthorized bool
2308 }{
2309 {"NoAllowedGroups", []string{}, []string{}, false},
2310 {"NoAllowedGroupsUserHasGroups", []string{}, []string{"a", "b"}, false},
2311 {"UserInAllowedGroup", []string{"a"}, []string{"a", "b"}, false},
2312 {"UserNotInAllowedGroup", []string{"a"}, []string{"c"}, true},
2315 for _, tt := range tests {
2316 t.Run(tt.name, func(t *testing.T) {
2317 emailAddress := "test"
2318 created := time.Now()
2320 session := &sessions.SessionState{
2321 Groups: tt.groups,
2322 Email: emailAddress,
2323 AccessToken: "oauth_token",
2324 CreatedAt: &created,
2327 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
2328 opts.AllowedGroups = tt.allowedGroups
2329 })
2330 if err != nil {
2331 t.Fatal(err)
2334 err = test.SaveSession(session)
2335 assert.NoError(t, err)
2337 test.proxy.ServeHTTP(test.rw, test.req)
2339 if tt.expectUnauthorized {
2340 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
2341 } else {
2342 assert.Equal(t, http.StatusAccepted, test.rw.Code)
2344 })