git.iko.soy/typeable/oauth2-proxy
on 71dc702 help sign in
typeable/oauth2-proxy/oauthproxy_test.go
2260 lines 62 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 TestStripAuthHeaders(t *testing.T) {
596 testCases := map[string]struct {
597 SkipAuthStripHeaders bool
598 PassBasicAuth bool
599 PassUserHeaders bool
600 PassAccessToken bool
601 PassAuthorization bool
602 StrippedHeaders map[string]bool
603 }{
604 "Default options": {
605 SkipAuthStripHeaders: true,
606 PassBasicAuth: true,
607 PassUserHeaders: true,
608 PassAccessToken: false,
609 PassAuthorization: false,
610 StrippedHeaders: map[string]bool{
611 "X-Forwarded-User": true,
612 "X-Forwarded-Email": true,
613 "X-Forwarded-Preferred-Username": true,
614 "X-Forwarded-Access-Token": false,
615 "Authorization": true,
616 },
617 },
618 "Pass access token": {
619 SkipAuthStripHeaders: true,
620 PassBasicAuth: true,
621 PassUserHeaders: true,
622 PassAccessToken: true,
623 PassAuthorization: false,
624 StrippedHeaders: map[string]bool{
625 "X-Forwarded-User": true,
626 "X-Forwarded-Email": true,
627 "X-Forwarded-Preferred-Username": true,
628 "X-Forwarded-Access-Token": true,
629 "Authorization": true,
630 },
631 },
632 "Nothing setting Authorization": {
633 SkipAuthStripHeaders: true,
634 PassBasicAuth: false,
635 PassUserHeaders: true,
636 PassAccessToken: true,
637 PassAuthorization: false,
638 StrippedHeaders: map[string]bool{
639 "X-Forwarded-User": true,
640 "X-Forwarded-Email": true,
641 "X-Forwarded-Preferred-Username": true,
642 "X-Forwarded-Access-Token": true,
643 "Authorization": false,
644 },
645 },
646 "Only Authorization header modified": {
647 SkipAuthStripHeaders: true,
648 PassBasicAuth: false,
649 PassUserHeaders: false,
650 PassAccessToken: false,
651 PassAuthorization: true,
652 StrippedHeaders: map[string]bool{
653 "X-Forwarded-User": false,
654 "X-Forwarded-Email": false,
655 "X-Forwarded-Preferred-Username": false,
656 "X-Forwarded-Access-Token": false,
657 "Authorization": true,
658 },
659 },
660 "Don't strip any headers (default options)": {
661 SkipAuthStripHeaders: false,
662 PassBasicAuth: true,
663 PassUserHeaders: true,
664 PassAccessToken: false,
665 PassAuthorization: false,
666 StrippedHeaders: map[string]bool{
667 "X-Forwarded-User": false,
668 "X-Forwarded-Email": false,
669 "X-Forwarded-Preferred-Username": false,
670 "X-Forwarded-Access-Token": false,
671 "Authorization": false,
672 },
673 },
674 "Don't strip any headers (custom options)": {
675 SkipAuthStripHeaders: false,
676 PassBasicAuth: true,
677 PassUserHeaders: true,
678 PassAccessToken: true,
679 PassAuthorization: false,
680 StrippedHeaders: map[string]bool{
681 "X-Forwarded-User": false,
682 "X-Forwarded-Email": false,
683 "X-Forwarded-Preferred-Username": false,
684 "X-Forwarded-Access-Token": false,
685 "Authorization": false,
686 },
687 },
688 }
690 initialHeaders := map[string]string{
691 "X-Forwarded-User": "9fcab5c9b889a557",
692 "X-Forwarded-Email": "john.doe@example.com",
693 "X-Forwarded-Preferred-Username": "john.doe",
694 "X-Forwarded-Access-Token": "AccessToken",
695 "Authorization": "bearer IDToken",
696 }
698 for name, tc := range testCases {
699 t.Run(name, func(t *testing.T) {
700 opts := baseTestOptions()
701 opts.SkipAuthStripHeaders = tc.SkipAuthStripHeaders
702 opts.PassBasicAuth = tc.PassBasicAuth
703 opts.PassUserHeaders = tc.PassUserHeaders
704 opts.PassAccessToken = tc.PassAccessToken
705 opts.PassAuthorization = tc.PassAuthorization
706 err := validation.Validate(opts)
707 assert.NoError(t, err)
709 req, _ := http.NewRequest("GET", fmt.Sprintf("%s/testCase", opts.ProxyPrefix), nil)
710 for header, val := range initialHeaders {
711 req.Header.Set(header, val)
712 }
714 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
715 assert.NoError(t, err)
716 if proxy.skipAuthStripHeaders {
717 proxy.stripAuthHeaders(req)
718 }
720 for header, stripped := range tc.StrippedHeaders {
721 if stripped {
722 assert.Equal(t, req.Header.Get(header), "")
723 } else {
724 assert.Equal(t, req.Header.Get(header), initialHeaders[header])
725 }
726 }
727 })
728 }
731type PassAccessTokenTest struct {
732 providerServer *httptest.Server
733 proxy *OAuthProxy
734 opts *options.Options
737type PassAccessTokenTestOptions struct {
738 PassAccessToken bool
739 ProxyUpstream options.Upstream
742func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTest, error) {
743 patt := &PassAccessTokenTest{}
745 patt.providerServer = httptest.NewServer(
746 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
747 var payload string
748 switch r.URL.Path {
749 case "/oauth/token":
750 payload = `{"access_token": "my_auth_token"}`
751 default:
752 payload = r.Header.Get("X-Forwarded-Access-Token")
753 if payload == "" {
754 payload = "No access token found."
755 }
756 }
757 w.WriteHeader(200)
758 _, err := w.Write([]byte(payload))
759 if err != nil {
760 panic(err)
761 }
762 }))
764 patt.opts = baseTestOptions()
765 patt.opts.UpstreamServers = options.Upstreams{
766 {
767 ID: patt.providerServer.URL,
768 Path: "/",
769 URI: patt.providerServer.URL,
770 },
771 }
772 if opts.ProxyUpstream.ID != "" {
773 patt.opts.UpstreamServers = append(patt.opts.UpstreamServers, opts.ProxyUpstream)
774 }
776 patt.opts.Cookie.Secure = false
777 patt.opts.PassAccessToken = opts.PassAccessToken
778 err := validation.Validate(patt.opts)
779 if err != nil {
780 return nil, err
781 }
783 providerURL, _ := url.Parse(patt.providerServer.URL)
784 const emailAddress = "michael.bland@gsa.gov"
786 patt.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
787 patt.proxy, err = NewOAuthProxy(patt.opts, func(email string) bool {
788 return email == emailAddress
789 })
790 if err != nil {
791 return nil, err
792 }
793 return patt, nil
796func (patTest *PassAccessTokenTest) Close() {
797 patTest.providerServer.Close()
800func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
801 cookie string) {
802 rw := httptest.NewRecorder()
803 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
804 strings.NewReader(""))
805 if err != nil {
806 return 0, ""
807 }
808 req.AddCookie(patTest.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
809 patTest.proxy.ServeHTTP(rw, req)
810 return rw.Code, rw.Header().Values("Set-Cookie")[1]
813// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
814// and cookie and returns body and status code.
815func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
816 cookieName := patTest.proxy.CookieName
817 var value string
818 keyPrefix := cookieName + "="
820 for _, field := range strings.Split(cookie, "; ") {
821 value = strings.TrimPrefix(field, keyPrefix)
822 if value != field {
823 break
824 } else {
825 value = ""
826 }
827 }
828 if value == "" {
829 return 0, ""
830 }
832 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
833 if err != nil {
834 return 0, ""
835 }
836 req.AddCookie(&http.Cookie{
837 Name: cookieName,
838 Value: value,
839 Path: "/",
840 Expires: time.Now().Add(time.Duration(24)),
841 HttpOnly: true,
842 })
844 rw := httptest.NewRecorder()
845 patTest.proxy.ServeHTTP(rw, req)
846 return rw.Code, rw.Body.String()
849func TestForwardAccessTokenUpstream(t *testing.T) {
850 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
851 PassAccessToken: true,
852 })
853 if err != nil {
854 t.Fatal(err)
855 }
856 t.Cleanup(patTest.Close)
858 // A successful validation will redirect and set the auth cookie.
859 code, cookie := patTest.getCallbackEndpoint()
860 if code != 302 {
861 t.Fatalf("expected 302; got %d", code)
862 }
863 assert.NotNil(t, cookie)
865 // Now we make a regular request; the access_token from the cookie is
866 // forwarded as the "X-Forwarded-Access-Token" header. The token is
867 // read by the test provider server and written in the response body.
868 code, payload := patTest.getEndpointWithCookie(cookie, "/")
869 if code != 200 {
870 t.Fatalf("expected 200; got %d", code)
871 }
872 assert.Equal(t, "my_auth_token", payload)
875func TestStaticProxyUpstream(t *testing.T) {
876 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
877 PassAccessToken: true,
878 ProxyUpstream: options.Upstream{
879 ID: "static-proxy",
880 Path: "/static-proxy",
881 Static: true,
882 },
883 })
884 if err != nil {
885 t.Fatal(err)
886 }
887 t.Cleanup(patTest.Close)
889 // A successful validation will redirect and set the auth cookie.
890 code, cookie := patTest.getCallbackEndpoint()
891 if code != 302 {
892 t.Fatalf("expected 302; got %d", code)
893 }
894 assert.NotEqual(t, nil, cookie)
896 // Now we make a regular request against the upstream proxy; And validate
897 // the returned status code through the static proxy.
898 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
899 if code != 200 {
900 t.Fatalf("expected 200; got %d", code)
901 }
902 assert.Equal(t, "Authenticated", payload)
905func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
906 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
907 PassAccessToken: false,
908 })
909 if err != nil {
910 t.Fatal(err)
911 }
912 t.Cleanup(patTest.Close)
914 // A successful validation will redirect and set the auth cookie.
915 code, cookie := patTest.getCallbackEndpoint()
916 if code != 302 {
917 t.Fatalf("expected 302; got %d", code)
918 }
919 assert.NotEqual(t, nil, cookie)
921 // Now we make a regular request, but the access token header should
922 // not be present.
923 code, payload := patTest.getEndpointWithCookie(cookie, "/")
924 if code != 200 {
925 t.Fatalf("expected 200; got %d", code)
926 }
927 assert.Equal(t, "No access token found.", payload)
930type SignInPageTest struct {
931 opts *options.Options
932 proxy *OAuthProxy
933 signInRegexp *regexp.Regexp
934 signInProviderRegexp *regexp.Regexp
937const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
938const signInSkipProvider = `>Found<`
940func NewSignInPageTest(skipProvider bool) (*SignInPageTest, error) {
941 var sipTest SignInPageTest
943 sipTest.opts = baseTestOptions()
944 sipTest.opts.SkipProviderButton = skipProvider
945 err := validation.Validate(sipTest.opts)
946 if err != nil {
947 return nil, err
948 }
950 sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
951 return true
952 })
953 if err != nil {
954 return nil, err
955 }
956 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
957 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
959 return &sipTest, nil
962func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
963 rw := httptest.NewRecorder()
964 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
965 sipTest.proxy.ServeHTTP(rw, req)
966 return rw.Code, rw.Body.String()
969func TestSignInPageIncludesTargetRedirect(t *testing.T) {
970 sipTest, err := NewSignInPageTest(false)
971 if err != nil {
972 t.Fatal(err)
973 }
974 const endpoint = "/some/random/endpoint"
976 code, body := sipTest.GetEndpoint(endpoint)
977 assert.Equal(t, 403, code)
979 match := sipTest.signInRegexp.FindStringSubmatch(body)
980 if match == nil {
981 t.Fatal("Did not find pattern in body: " +
982 signInRedirectPattern + "\nBody:\n" + body)
983 }
984 if match[1] != endpoint {
985 t.Fatal(`expected redirect to "` + endpoint +
986 `", but was "` + match[1] + `"`)
987 }
990func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
991 sipTest, err := NewSignInPageTest(false)
992 if err != nil {
993 t.Fatal(err)
994 }
995 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
996 assert.Equal(t, 200, code)
998 match := sipTest.signInRegexp.FindStringSubmatch(body)
999 if match == nil {
1000 t.Fatal("Did not find pattern in body: " +
1001 signInRedirectPattern + "\nBody:\n" + body)
1003 if match[1] != "/" {
1004 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
1008func TestSignInPageSkipProvider(t *testing.T) {
1009 sipTest, err := NewSignInPageTest(true)
1010 if err != nil {
1011 t.Fatal(err)
1014 endpoint := "/some/random/endpoint"
1016 code, body := sipTest.GetEndpoint(endpoint)
1017 assert.Equal(t, 302, code)
1019 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
1020 if match == nil {
1021 t.Fatal("Did not find pattern in body: " +
1022 signInSkipProvider + "\nBody:\n" + body)
1026func TestSignInPageSkipProviderDirect(t *testing.T) {
1027 sipTest, err := NewSignInPageTest(true)
1028 if err != nil {
1029 t.Fatal(err)
1032 endpoint := "/sign_in"
1034 code, body := sipTest.GetEndpoint(endpoint)
1035 assert.Equal(t, 302, code)
1037 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
1038 if match == nil {
1039 t.Fatal("Did not find pattern in body: " +
1040 signInSkipProvider + "\nBody:\n" + body)
1044type ProcessCookieTest struct {
1045 opts *options.Options
1046 proxy *OAuthProxy
1047 rw *httptest.ResponseRecorder
1048 req *http.Request
1049 validateUser bool
1052type ProcessCookieTestOpts struct {
1053 providerValidateCookieResponse bool
1056type OptionsModifier func(*options.Options)
1058func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1059 var pcTest ProcessCookieTest
1061 pcTest.opts = baseTestOptions()
1062 for _, modifier := range modifiers {
1063 modifier(pcTest.opts)
1065 // First, set the CookieRefresh option so proxy.AesCipher is created,
1066 // needed to encrypt the access_token.
1067 pcTest.opts.Cookie.Refresh = time.Hour
1068 err := validation.Validate(pcTest.opts)
1069 if err != nil {
1070 return nil, err
1073 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1074 return pcTest.validateUser
1075 })
1076 if err != nil {
1077 return nil, err
1079 pcTest.proxy.provider = &TestProvider{
1080 ValidToken: opts.providerValidateCookieResponse,
1083 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
1084 // access_token validation.
1085 pcTest.proxy.CookieRefresh = time.Duration(0)
1086 pcTest.rw = httptest.NewRecorder()
1087 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
1088 pcTest.validateUser = true
1089 return &pcTest, nil
1092func NewProcessCookieTestWithDefaults() (*ProcessCookieTest, error) {
1093 return NewProcessCookieTest(ProcessCookieTestOpts{
1094 providerValidateCookieResponse: true,
1095 })
1098func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1099 return NewProcessCookieTest(ProcessCookieTestOpts{
1100 providerValidateCookieResponse: true,
1101 }, modifiers...)
1104func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
1105 err := p.proxy.SaveSession(p.rw, p.req, s)
1106 if err != nil {
1107 return err
1109 for _, cookie := range p.rw.Result().Cookies() {
1110 p.req.AddCookie(cookie)
1112 return nil
1115func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
1116 return p.proxy.LoadCookiedSession(p.req)
1119func TestLoadCookiedSession(t *testing.T) {
1120 pcTest, err := NewProcessCookieTestWithDefaults()
1121 if err != nil {
1122 t.Fatal(err)
1125 created := time.Now()
1126 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
1127 err = pcTest.SaveSession(startSession)
1128 assert.NoError(t, err)
1130 session, err := pcTest.LoadCookiedSession()
1131 if err != nil {
1132 t.Fatal(err)
1134 assert.Equal(t, startSession.Email, session.Email)
1135 assert.Equal(t, "", session.User)
1136 assert.Equal(t, startSession.AccessToken, session.AccessToken)
1139func TestProcessCookieNoCookieError(t *testing.T) {
1140 pcTest, err := NewProcessCookieTestWithDefaults()
1141 if err != nil {
1142 t.Fatal(err)
1145 session, err := pcTest.LoadCookiedSession()
1146 assert.Error(t, err, "cookie \"_oauth2_proxy\" not present")
1147 if session != nil {
1148 t.Errorf("expected nil session. got %#v", session)
1152func TestProcessCookieRefreshNotSet(t *testing.T) {
1153 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1154 opts.Cookie.Expire = time.Duration(23) * time.Hour
1155 })
1156 if err != nil {
1157 t.Fatal(err)
1160 reference := time.Now().Add(time.Duration(-2) * time.Hour)
1162 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1163 err = pcTest.SaveSession(startSession)
1164 assert.NoError(t, err)
1166 session, err := pcTest.LoadCookiedSession()
1167 assert.Equal(t, nil, err)
1168 if session.Age() < time.Duration(-2)*time.Hour {
1169 t.Errorf("cookie too young %v", session.Age())
1171 assert.Equal(t, startSession.Email, session.Email)
1174func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1175 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1176 opts.Cookie.Expire = time.Duration(24) * time.Hour
1177 })
1178 if err != nil {
1179 t.Fatal(err)
1182 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1183 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1184 err = pcTest.SaveSession(startSession)
1185 assert.NoError(t, err)
1187 session, err := pcTest.LoadCookiedSession()
1188 assert.NotEqual(t, nil, err)
1189 if session != nil {
1190 t.Errorf("expected nil session %#v", session)
1194func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1195 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1196 opts.Cookie.Expire = time.Duration(24) * time.Hour
1197 })
1198 if err != nil {
1199 t.Fatal(err)
1202 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1203 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1204 err = pcTest.SaveSession(startSession)
1205 assert.NoError(t, err)
1207 pcTest.proxy.CookieRefresh = time.Hour
1208 session, err := pcTest.LoadCookiedSession()
1209 assert.NotEqual(t, nil, err)
1210 if session != nil {
1211 t.Errorf("expected nil session %#v", session)
1215func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
1216 pcTest, err := NewProcessCookieTestWithDefaults()
1217 if err != nil {
1218 return nil, err
1220 pcTest.req, _ = http.NewRequest("GET",
1221 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1222 return pcTest, nil
1225func TestUserInfoEndpointAccepted(t *testing.T) {
1226 test, err := NewUserInfoEndpointTest()
1227 if err != nil {
1228 t.Fatal(err)
1231 startSession := &sessions.SessionState{
1232 Email: "john.doe@example.com", AccessToken: "my_access_token"}
1233 err = test.SaveSession(startSession)
1234 assert.NoError(t, err)
1236 test.proxy.ServeHTTP(test.rw, test.req)
1237 assert.Equal(t, http.StatusOK, test.rw.Code)
1238 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1239 assert.Equal(t, "{\"email\":\"john.doe@example.com\"}\n", string(bodyBytes))
1242func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1243 test, err := NewUserInfoEndpointTest()
1244 if err != nil {
1245 t.Fatal(err)
1248 test.proxy.ServeHTTP(test.rw, test.req)
1249 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1252func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1253 pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1254 if err != nil {
1255 return nil, err
1257 pcTest.req, _ = http.NewRequest("GET",
1258 pcTest.opts.ProxyPrefix+"/auth", nil)
1259 return pcTest, nil
1262func TestAuthOnlyEndpointAccepted(t *testing.T) {
1263 test, err := NewAuthOnlyEndpointTest()
1264 if err != nil {
1265 t.Fatal(err)
1268 created := time.Now()
1269 startSession := &sessions.SessionState{
1270 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1271 err = test.SaveSession(startSession)
1272 assert.NoError(t, err)
1274 test.proxy.ServeHTTP(test.rw, test.req)
1275 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1276 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1277 assert.Equal(t, "", string(bodyBytes))
1280func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1281 test, err := NewAuthOnlyEndpointTest()
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)
1288 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1289 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1292func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1293 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1294 opts.Cookie.Expire = time.Duration(24) * time.Hour
1295 })
1296 if err != nil {
1297 t.Fatal(err)
1300 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1301 startSession := &sessions.SessionState{
1302 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1303 err = test.SaveSession(startSession)
1304 assert.NoError(t, err)
1306 test.proxy.ServeHTTP(test.rw, test.req)
1307 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1308 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1309 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1312func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1313 test, err := NewAuthOnlyEndpointTest()
1314 if err != nil {
1315 t.Fatal(err)
1318 created := time.Now()
1319 startSession := &sessions.SessionState{
1320 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1321 err = test.SaveSession(startSession)
1322 assert.NoError(t, err)
1323 test.validateUser = false
1325 test.proxy.ServeHTTP(test.rw, test.req)
1326 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1327 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1328 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1331func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1332 var pcTest ProcessCookieTest
1334 pcTest.opts = baseTestOptions()
1335 pcTest.opts.SetXAuthRequest = true
1336 err := validation.Validate(pcTest.opts)
1337 assert.NoError(t, err)
1339 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1340 return pcTest.validateUser
1341 })
1342 if err != nil {
1343 t.Fatal(err)
1345 pcTest.proxy.provider = &TestProvider{
1346 ValidToken: true,
1349 pcTest.validateUser = true
1351 pcTest.rw = httptest.NewRecorder()
1352 pcTest.req, _ = http.NewRequest("GET",
1353 pcTest.opts.ProxyPrefix+"/auth", nil)
1355 created := time.Now()
1356 startSession := &sessions.SessionState{
1357 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1358 err = pcTest.SaveSession(startSession)
1359 assert.NoError(t, err)
1361 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1362 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1363 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1364 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1367func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1368 var pcTest ProcessCookieTest
1370 pcTest.opts = baseTestOptions()
1371 pcTest.opts.SetXAuthRequest = true
1372 pcTest.opts.SetBasicAuth = true
1373 err := validation.Validate(pcTest.opts)
1374 assert.NoError(t, err)
1376 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1377 return pcTest.validateUser
1378 })
1379 if err != nil {
1380 t.Fatal(err)
1382 pcTest.proxy.provider = &TestProvider{
1383 ValidToken: true,
1386 pcTest.validateUser = true
1388 pcTest.rw = httptest.NewRecorder()
1389 pcTest.req, _ = http.NewRequest("GET",
1390 pcTest.opts.ProxyPrefix+"/auth", nil)
1392 created := time.Now()
1393 startSession := &sessions.SessionState{
1394 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1395 err = pcTest.SaveSession(startSession)
1396 assert.NoError(t, err)
1398 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1399 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1400 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1401 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1402 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:"+pcTest.opts.BasicAuthPassword))
1403 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1406func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1407 var pcTest ProcessCookieTest
1409 pcTest.opts = baseTestOptions()
1410 pcTest.opts.SetXAuthRequest = true
1411 pcTest.opts.SetBasicAuth = false
1412 err := validation.Validate(pcTest.opts)
1413 assert.NoError(t, err)
1415 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1416 return pcTest.validateUser
1417 })
1418 if err != nil {
1419 t.Fatal(err)
1421 pcTest.proxy.provider = &TestProvider{
1422 ValidToken: true,
1425 pcTest.validateUser = true
1427 pcTest.rw = httptest.NewRecorder()
1428 pcTest.req, _ = http.NewRequest("GET",
1429 pcTest.opts.ProxyPrefix+"/auth", nil)
1431 created := time.Now()
1432 startSession := &sessions.SessionState{
1433 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1434 err = pcTest.SaveSession(startSession)
1435 assert.NoError(t, err)
1437 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1438 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1439 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1440 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1441 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1444func TestAuthSkippedForPreflightRequests(t *testing.T) {
1445 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1446 w.WriteHeader(200)
1447 _, err := w.Write([]byte("response"))
1448 if err != nil {
1449 t.Fatal(err)
1451 }))
1452 t.Cleanup(upstream.Close)
1454 opts := baseTestOptions()
1455 opts.UpstreamServers = options.Upstreams{
1457 ID: upstream.URL,
1458 Path: "/",
1459 URI: upstream.URL,
1460 },
1462 opts.SkipAuthPreflight = true
1463 err := validation.Validate(opts)
1464 assert.NoError(t, err)
1466 upstreamURL, _ := url.Parse(upstream.URL)
1467 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1469 proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
1470 if err != nil {
1471 t.Fatal(err)
1473 rw := httptest.NewRecorder()
1474 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1475 proxy.ServeHTTP(rw, req)
1477 assert.Equal(t, 200, rw.Code)
1478 assert.Equal(t, "response", rw.Body.String())
1481type SignatureAuthenticator struct {
1482 auth hmacauth.HmacAuth
1485func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1486 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1488 var msg string
1489 switch result {
1490 case hmacauth.ResultNoSignature:
1491 msg = "no signature received"
1492 case hmacauth.ResultMatch:
1493 msg = "signatures match"
1494 case hmacauth.ResultMismatch:
1495 msg = fmt.Sprintf(
1496 "signatures do not match:\n received: %s\n computed: %s",
1497 headerSig,
1498 computedSig)
1499 default:
1500 panic("unknown result value: " + result.String())
1503 _, err := w.Write([]byte(msg))
1504 if err != nil {
1505 panic(err)
1509type SignatureTest struct {
1510 opts *options.Options
1511 upstream *httptest.Server
1512 upstreamHost string
1513 provider *httptest.Server
1514 header http.Header
1515 rw *httptest.ResponseRecorder
1516 authenticator *SignatureAuthenticator
1519func NewSignatureTest() (*SignatureTest, error) {
1520 opts := baseTestOptions()
1521 opts.EmailDomains = []string{"acm.org"}
1523 authenticator := &SignatureAuthenticator{}
1524 upstream := httptest.NewServer(
1525 http.HandlerFunc(authenticator.Authenticate))
1526 upstreamURL, err := url.Parse(upstream.URL)
1527 if err != nil {
1528 return nil, err
1530 opts.UpstreamServers = options.Upstreams{
1532 ID: upstream.URL,
1533 Path: "/",
1534 URI: upstream.URL,
1535 },
1538 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1539 _, err := w.Write([]byte(`{"access_token": "my_auth_token"}`))
1540 if err != nil {
1541 panic(err)
1544 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1545 providerURL, err := url.Parse(provider.URL)
1546 if err != nil {
1547 return nil, err
1549 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1551 return &SignatureTest{
1552 opts,
1553 upstream,
1554 upstreamURL.Host,
1555 provider,
1556 make(http.Header),
1557 httptest.NewRecorder(),
1558 authenticator,
1559 }, nil
1562func (st *SignatureTest) Close() {
1563 st.provider.Close()
1564 st.upstream.Close()
1567// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1568// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1569// https://github.com/18F/hmacauth/pull/4
1570type fakeNetConn struct {
1571 reqBody string
1574func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1575 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1576 copy(p, fnc.reqBody)
1577 fnc.reqBody = ""
1578 return bodyLen, io.EOF
1580 return 0, io.EOF
1583func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) error {
1584 err := validation.Validate(st.opts)
1585 if err != nil {
1586 return err
1588 proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
1589 if err != nil {
1590 return err
1593 var bodyBuf io.ReadCloser
1594 if body != "" {
1595 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1597 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1598 req.Header = st.header
1600 state := &sessions.SessionState{
1601 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1602 err = proxy.SaveSession(st.rw, req, state)
1603 if err != nil {
1604 return err
1606 for _, c := range st.rw.Result().Cookies() {
1607 req.AddCookie(c)
1609 // This is used by the upstream to validate the signature.
1610 st.authenticator.auth = hmacauth.NewHmacAuth(
1611 crypto.SHA1, []byte(key), upstream.SignatureHeader, upstream.SignatureHeaders)
1612 proxy.ServeHTTP(st.rw, req)
1614 return nil
1617func TestRequestSignature(t *testing.T) {
1618 testCases := map[string]struct {
1619 method string
1620 body string
1621 key string
1622 resp string
1623 }{
1624 "No request signature": {
1625 method: "GET",
1626 body: "",
1627 key: "",
1628 resp: "no signature received",
1629 },
1630 "Get request": {
1631 method: "GET",
1632 body: "",
1633 key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1634 resp: "signatures match",
1635 },
1636 "Post request": {
1637 method: "POST",
1638 body: `{ "hello": "world!" }`,
1639 key: "d90df39e2d19282840252612dd7c81421a372f61",
1640 resp: "signatures match",
1641 },
1643 for name, tc := range testCases {
1644 t.Run(name, func(t *testing.T) {
1645 st, err := NewSignatureTest()
1646 if err != nil {
1647 t.Fatal(err)
1649 t.Cleanup(st.Close)
1650 if tc.key != "" {
1651 st.opts.SignatureKey = fmt.Sprintf("sha1:%s", tc.key)
1653 err = st.MakeRequestWithExpectedKey(tc.method, tc.body, tc.key)
1654 assert.NoError(t, err)
1655 assert.Equal(t, 200, st.rw.Code)
1656 assert.Equal(t, tc.resp, st.rw.Body.String())
1657 })
1661func TestGetRedirect(t *testing.T) {
1662 opts := baseTestOptions()
1663 err := validation.Validate(opts)
1664 assert.NoError(t, err)
1665 require.NotEmpty(t, opts.ProxyPrefix)
1666 proxy, err := NewOAuthProxy(opts, func(s string) bool { return false })
1667 if err != nil {
1668 t.Fatal(err)
1671 tests := []struct {
1672 name string
1673 url string
1674 expectedRedirect string
1675 }{
1677 name: "request outside of ProxyPrefix redirects to original URL",
1678 url: "/foo/bar",
1679 expectedRedirect: "/foo/bar",
1680 },
1682 name: "request under ProxyPrefix redirects to root",
1683 url: proxy.ProxyPrefix + "/foo/bar",
1684 expectedRedirect: "/",
1685 },
1687 for _, tt := range tests {
1688 t.Run(tt.name, func(t *testing.T) {
1689 req, _ := http.NewRequest("GET", tt.url, nil)
1690 redirect, err := proxy.GetRedirect(req)
1692 assert.NoError(t, err)
1693 assert.Equal(t, tt.expectedRedirect, redirect)
1694 })
1698type ajaxRequestTest struct {
1699 opts *options.Options
1700 proxy *OAuthProxy
1703func newAjaxRequestTest() (*ajaxRequestTest, error) {
1704 test := &ajaxRequestTest{}
1705 test.opts = baseTestOptions()
1706 err := validation.Validate(test.opts)
1707 if err != nil {
1708 return nil, err
1711 test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
1712 return true
1713 })
1714 if err != nil {
1715 return nil, err
1717 return test, nil
1720func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1721 rw := httptest.NewRecorder()
1722 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1723 if err != nil {
1724 return 0, nil, err
1726 req.Header = header
1727 test.proxy.ServeHTTP(rw, req)
1728 return rw.Code, rw.Header(), nil
1731func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1732 test, err := newAjaxRequestTest()
1733 if err != nil {
1734 t.Fatal(err)
1736 endpoint := "/test"
1738 code, rh, err := test.getEndpoint(endpoint, header)
1739 assert.NoError(t, err)
1740 assert.Equal(t, http.StatusUnauthorized, code)
1741 mime := rh.Get("Content-Type")
1742 assert.Equal(t, applicationJSON, mime)
1744func TestAjaxUnauthorizedRequest1(t *testing.T) {
1745 header := make(http.Header)
1746 header.Add("accept", applicationJSON)
1748 testAjaxUnauthorizedRequest(t, header)
1751func TestAjaxUnauthorizedRequest2(t *testing.T) {
1752 header := make(http.Header)
1753 header.Add("Accept", applicationJSON)
1755 testAjaxUnauthorizedRequest(t, header)
1758func TestAjaxForbiddendRequest(t *testing.T) {
1759 test, err := newAjaxRequestTest()
1760 if err != nil {
1761 t.Fatal(err)
1763 endpoint := "/test"
1764 header := make(http.Header)
1765 code, rh, err := test.getEndpoint(endpoint, header)
1766 assert.NoError(t, err)
1767 assert.Equal(t, http.StatusForbidden, code)
1768 mime := rh.Get("Content-Type")
1769 assert.NotEqual(t, applicationJSON, mime)
1772func TestClearSplitCookie(t *testing.T) {
1773 opts := baseTestOptions()
1774 opts.Cookie.Secret = base64CookieSecret
1775 opts.Cookie.Name = "oauth2"
1776 opts.Cookie.Domains = []string{"abc"}
1777 err := validation.Validate(opts)
1778 assert.NoError(t, err)
1780 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1781 if err != nil {
1782 t.Fatal(err)
1785 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1786 var rw = httptest.NewRecorder()
1787 req := httptest.NewRequest("get", "/", nil)
1789 req.AddCookie(&http.Cookie{
1790 Name: "test1",
1791 Value: "test1",
1792 })
1793 req.AddCookie(&http.Cookie{
1794 Name: "oauth2_0",
1795 Value: "oauth2_0",
1796 })
1797 req.AddCookie(&http.Cookie{
1798 Name: "oauth2_1",
1799 Value: "oauth2_1",
1800 })
1802 err = p.ClearSessionCookie(rw, req)
1803 assert.NoError(t, err)
1804 header := rw.Header()
1806 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1809func TestClearSingleCookie(t *testing.T) {
1810 opts := baseTestOptions()
1811 opts.Cookie.Name = "oauth2"
1812 opts.Cookie.Domains = []string{"abc"}
1813 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1814 if err != nil {
1815 t.Fatal(err)
1818 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1819 var rw = httptest.NewRecorder()
1820 req := httptest.NewRequest("get", "/", nil)
1822 req.AddCookie(&http.Cookie{
1823 Name: "test1",
1824 Value: "test1",
1825 })
1826 req.AddCookie(&http.Cookie{
1827 Name: "oauth2",
1828 Value: "oauth2",
1829 })
1831 err = p.ClearSessionCookie(rw, req)
1832 assert.NoError(t, err)
1833 header := rw.Header()
1835 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1838type NoOpKeySet struct {
1841func (NoOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
1842 splitStrings := strings.Split(jwt, ".")
1843 payloadString := splitStrings[1]
1844 return base64.RawURLEncoding.DecodeString(payloadString)
1847func TestGetJwtSession(t *testing.T) {
1848 /* token payload:
1850 "sub": "1234567890",
1851 "aud": "https://test.myapp.com",
1852 "name": "John Doe",
1853 "email": "john@example.com",
1854 "iss": "https://issuer.example.com",
1855 "iat": 1553691215,
1856 "exp": 1912151821
1858 */
1859 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1860 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1861 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1862 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1863 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1864 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1866 keyset := NoOpKeySet{}
1867 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1868 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1870 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1871 opts.PassAuthorization = true
1872 opts.SetAuthorization = true
1873 opts.SetXAuthRequest = true
1874 opts.SkipJwtBearerTokens = true
1875 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
1876 })
1877 if err != nil {
1878 t.Fatal(err)
1880 tp, _ := test.proxy.provider.(*TestProvider)
1881 tp.GroupValidator = func(s string) bool {
1882 return true
1885 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1886 test.req.Header = map[string][]string{
1887 "Authorization": {authHeader},
1890 // Bearer
1891 expires := time.Unix(1912151821, 0)
1892 session, err := test.proxy.GetJwtSession(test.req)
1893 assert.NoError(t, err)
1894 assert.Equal(t, session.User, "1234567890")
1895 assert.Equal(t, session.Email, "john@example.com")
1896 assert.Equal(t, session.ExpiresOn, &expires)
1897 assert.Equal(t, session.IDToken, goodJwt)
1899 test.proxy.ServeHTTP(test.rw, test.req)
1900 if test.rw.Code >= 400 {
1901 t.Fatalf("expected 3xx got %d", test.rw.Code)
1904 // Check PassAuthorization, should overwrite Basic header
1905 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
1906 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "1234567890")
1907 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
1909 // SetAuthorization and SetXAuthRequest
1910 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
1911 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "1234567890")
1912 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
1915func TestFindJwtBearerToken(t *testing.T) {
1916 p := OAuthProxy{CookieName: "oauth2", CookieDomains: []string{"abc"}}
1917 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}}
1919 validToken := "eyJfoobar.eyJfoobar.12345asdf"
1920 var token string
1922 // Bearer
1923 getReq.Header = map[string][]string{
1924 "Authorization": {fmt.Sprintf("Bearer %s", validToken)},
1927 token, err := p.findBearerToken(getReq)
1928 assert.NoError(t, err)
1929 assert.Equal(t, validToken, token)
1931 // Basic - no password
1932 getReq.SetBasicAuth(token, "")
1933 token, err = p.findBearerToken(getReq)
1934 assert.NoError(t, err)
1935 assert.Equal(t, validToken, token)
1937 // Basic - sentinel password
1938 getReq.SetBasicAuth(token, "x-oauth-basic")
1939 token, err = p.findBearerToken(getReq)
1940 assert.NoError(t, err)
1941 assert.Equal(t, validToken, token)
1943 // Basic - any username, password matching jwt pattern
1944 getReq.SetBasicAuth("any-username-you-could-wish-for", token)
1945 token, err = p.findBearerToken(getReq)
1946 assert.NoError(t, err)
1947 assert.Equal(t, validToken, token)
1949 failures := []string{
1950 // Too many parts
1951 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1952 // Not enough parts
1953 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA",
1954 // Invalid encrypted key
1955 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.//////.dGVzdA.dGVzdA.dGVzdA",
1956 // Invalid IV
1957 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.//////.dGVzdA.dGVzdA",
1958 // Invalid ciphertext
1959 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.//////.dGVzdA",
1960 // Invalid tag
1961 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.//////",
1962 // Invalid header
1963 "W10.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1964 // Invalid header
1965 "######.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1966 // Missing alc/enc params
1967 "e30.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1970 for _, failure := range failures {
1971 getReq.Header = map[string][]string{
1972 "Authorization": {fmt.Sprintf("Bearer %s", failure)},
1974 _, err := p.findBearerToken(getReq)
1975 assert.Error(t, err)
1979func Test_prepareNoCache(t *testing.T) {
1980 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1981 prepareNoCache(w)
1982 })
1983 mux := http.NewServeMux()
1984 mux.Handle("/", handler)
1986 rec := httptest.NewRecorder()
1987 req := httptest.NewRequest(http.MethodGet, "/", nil)
1988 mux.ServeHTTP(rec, req)
1990 for k, v := range noCacheHeaders {
1991 assert.Equal(t, rec.Header().Get(k), v)
1995func Test_noCacheHeaders(t *testing.T) {
1996 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1997 _, err := w.Write([]byte("upstream"))
1998 if err != nil {
1999 t.Error(err)
2001 }))
2002 t.Cleanup(upstream.Close)
2004 opts := baseTestOptions()
2005 opts.UpstreamServers = options.Upstreams{
2007 ID: upstream.URL,
2008 Path: "/",
2009 URI: upstream.URL,
2010 },
2012 opts.SkipAuthRegex = []string{".*"}
2013 err := validation.Validate(opts)
2014 assert.NoError(t, err)
2015 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2016 if err != nil {
2017 t.Fatal(err)
2020 t.Run("not exist in response from upstream", func(t *testing.T) {
2021 rec := httptest.NewRecorder()
2022 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
2023 proxy.ServeHTTP(rec, req)
2025 assert.Equal(t, http.StatusOK, rec.Code)
2026 assert.Equal(t, "upstream", rec.Body.String())
2028 // checking noCacheHeaders does not exists in response headers from upstream
2029 for k := range noCacheHeaders {
2030 assert.Equal(t, "", rec.Header().Get(k))
2032 })
2034 t.Run("has no-cache", func(t *testing.T) {
2035 tests := []struct {
2036 path string
2037 hasNoCache bool
2038 }{
2040 path: "/oauth2/sign_in",
2041 hasNoCache: true,
2042 },
2044 path: "/oauth2/sign_out",
2045 hasNoCache: true,
2046 },
2048 path: "/oauth2/start",
2049 hasNoCache: true,
2050 },
2052 path: "/oauth2/callback",
2053 hasNoCache: true,
2054 },
2056 path: "/oauth2/auth",
2057 hasNoCache: false,
2058 },
2060 path: "/oauth2/userinfo",
2061 hasNoCache: true,
2062 },
2064 path: "/upstream",
2065 hasNoCache: false,
2066 },
2069 for _, tt := range tests {
2070 t.Run(tt.path, func(t *testing.T) {
2071 rec := httptest.NewRecorder()
2072 req := httptest.NewRequest(http.MethodGet, tt.path, nil)
2073 proxy.ServeHTTP(rec, req)
2074 cacheControl := rec.Result().Header.Get("Cache-Control")
2075 if tt.hasNoCache != (strings.Contains(cacheControl, "no-cache")) {
2076 t.Errorf(`unexpected "Cache-Control" header: %s`, cacheControl)
2078 })
2081 })
2084func baseTestOptions() *options.Options {
2085 opts := options.NewOptions()
2086 opts.Cookie.Secret = rawCookieSecret
2087 opts.ClientID = clientID
2088 opts.ClientSecret = clientSecret
2089 opts.EmailDomains = []string{"*"}
2090 return opts
2093func TestTrustedIPs(t *testing.T) {
2094 tests := []struct {
2095 name string
2096 trustedIPs []string
2097 reverseProxy bool
2098 realClientIPHeader string
2099 req *http.Request
2100 expectTrusted bool
2101 }{
2102 // Check unconfigured behavior.
2104 name: "Default",
2105 trustedIPs: nil,
2106 reverseProxy: false,
2107 realClientIPHeader: "X-Real-IP", // Default value
2108 req: func() *http.Request {
2109 req, _ := http.NewRequest("GET", "/", nil)
2110 return req
2111 }(),
2112 expectTrusted: false,
2113 },
2114 // Check using req.RemoteAddr (Options.ReverseProxy == false).
2116 name: "WithRemoteAddr",
2117 trustedIPs: []string{"127.0.0.1"},
2118 reverseProxy: false,
2119 realClientIPHeader: "X-Real-IP", // Default value
2120 req: func() *http.Request {
2121 req, _ := http.NewRequest("GET", "/", nil)
2122 req.RemoteAddr = "127.0.0.1:43670"
2123 return req
2124 }(),
2125 expectTrusted: true,
2126 },
2127 // Check ignores req.RemoteAddr match when behind a reverse proxy / missing header.
2129 name: "IgnoresRemoteAddrInReverseProxyMode",
2130 trustedIPs: []string{"127.0.0.1"},
2131 reverseProxy: true,
2132 realClientIPHeader: "X-Real-IP", // Default value
2133 req: func() *http.Request {
2134 req, _ := http.NewRequest("GET", "/", nil)
2135 req.RemoteAddr = "127.0.0.1:44324"
2136 return req
2137 }(),
2138 expectTrusted: false,
2139 },
2140 // Check successful trusting of localhost in IPv4.
2142 name: "TrustsLocalhostInReverseProxyMode",
2143 trustedIPs: []string{"127.0.0.0/8", "::1"},
2144 reverseProxy: true,
2145 realClientIPHeader: "X-Forwarded-For",
2146 req: func() *http.Request {
2147 req, _ := http.NewRequest("GET", "/", nil)
2148 req.Header.Add("X-Forwarded-For", "127.0.0.1")
2149 return req
2150 }(),
2151 expectTrusted: true,
2152 },
2153 // Check successful trusting of localhost in IPv6.
2155 name: "TrustsIP6LocalostInReverseProxyMode",
2156 trustedIPs: []string{"127.0.0.0/8", "::1"},
2157 reverseProxy: true,
2158 realClientIPHeader: "X-Forwarded-For",
2159 req: func() *http.Request {
2160 req, _ := http.NewRequest("GET", "/", nil)
2161 req.Header.Add("X-Forwarded-For", "::1")
2162 return req
2163 }(),
2164 expectTrusted: true,
2165 },
2166 // Check does not trust random IPv4 address.
2168 name: "DoesNotTrustRandomIP4Address",
2169 trustedIPs: []string{"127.0.0.0/8", "::1"},
2170 reverseProxy: true,
2171 realClientIPHeader: "X-Forwarded-For",
2172 req: func() *http.Request {
2173 req, _ := http.NewRequest("GET", "/", nil)
2174 req.Header.Add("X-Forwarded-For", "12.34.56.78")
2175 return req
2176 }(),
2177 expectTrusted: false,
2178 },
2179 // Check does not trust random IPv6 address.
2181 name: "DoesNotTrustRandomIP6Address",
2182 trustedIPs: []string{"127.0.0.0/8", "::1"},
2183 reverseProxy: true,
2184 realClientIPHeader: "X-Forwarded-For",
2185 req: func() *http.Request {
2186 req, _ := http.NewRequest("GET", "/", nil)
2187 req.Header.Add("X-Forwarded-For", "::2")
2188 return req
2189 }(),
2190 expectTrusted: false,
2191 },
2192 // Check respects correct header.
2194 name: "RespectsCorrectHeaderInReverseProxyMode",
2195 trustedIPs: []string{"127.0.0.0/8", "::1"},
2196 reverseProxy: true,
2197 realClientIPHeader: "X-Forwarded-For",
2198 req: func() *http.Request {
2199 req, _ := http.NewRequest("GET", "/", nil)
2200 req.Header.Add("X-Real-IP", "::1")
2201 return req
2202 }(),
2203 expectTrusted: false,
2204 },
2205 // Check doesn't trust if garbage is provided.
2207 name: "DoesNotTrustGarbageInReverseProxyMode",
2208 trustedIPs: []string{"127.0.0.0/8", "::1"},
2209 reverseProxy: true,
2210 realClientIPHeader: "X-Forwarded-For",
2211 req: func() *http.Request {
2212 req, _ := http.NewRequest("GET", "/", nil)
2213 req.Header.Add("X-Forwarded-For", "adsfljk29242as!!")
2214 return req
2215 }(),
2216 expectTrusted: false,
2217 },
2218 // Check doesn't trust if garbage is provided (no reverse-proxy).
2220 name: "DoesNotTrustGarbage",
2221 trustedIPs: []string{"127.0.0.0/8", "::1"},
2222 reverseProxy: false,
2223 realClientIPHeader: "X-Real-IP",
2224 req: func() *http.Request {
2225 req, _ := http.NewRequest("GET", "/", nil)
2226 req.RemoteAddr = "adsfljk29242as!!"
2227 return req
2228 }(),
2229 expectTrusted: false,
2230 },
2233 for _, tt := range tests {
2234 t.Run(tt.name, func(t *testing.T) {
2235 opts := baseTestOptions()
2236 opts.UpstreamServers = options.Upstreams{
2238 ID: "static",
2239 Path: "/",
2240 Static: true,
2241 },
2243 opts.TrustedIPs = tt.trustedIPs
2244 opts.ReverseProxy = tt.reverseProxy
2245 opts.RealClientIPHeader = tt.realClientIPHeader
2246 validation.Validate(opts)
2248 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
2249 assert.NoError(t, err)
2250 rw := httptest.NewRecorder()
2252 proxy.ServeHTTP(rw, tt.req)
2253 if tt.expectTrusted {
2254 assert.Equal(t, 200, rw.Code)
2255 } else {
2256 assert.Equal(t, 403, rw.Code)
2258 })