git.iko.soy/typeable/oauth2-proxy
on proxy-mapping help sign in
typeable/oauth2-proxy/oauthproxy_test.go
2686 lines 68 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/v7/pkg/apis/options"
23 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
24 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
25 sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
26 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
27 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
28 "github.com/oauth2-proxy/oauth2-proxy/v7/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("./testdata/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(_ context.Context, _ *sessions.SessionState) (string, error) {
400 return tp.EmailAddress, nil
403func (tp *TestProvider) ValidateSessionState(_ context.Context, _ *sessions.SessionState) bool {
404 return tp.ValidToken
407func Test_redeemCode(t *testing.T) {
408 opts := baseTestOptions()
409 err := validation.Validate(opts)
410 assert.NoError(t, err)
412 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
413 if err != nil {
414 t.Fatal(err)
415 }
417 _, err = proxy.redeemCode(context.Background(), "www.example.com", "")
418 assert.Error(t, err)
421func Test_enrichSession(t *testing.T) {
422 const (
423 sessionUser = "Mr Session"
424 sessionEmail = "session@example.com"
425 providerEmail = "provider@example.com"
426 )
428 testCases := map[string]struct {
429 session *sessions.SessionState
430 expectedUser string
431 expectedEmail string
432 }{
433 "Session already has enrichable fields": {
434 session: &sessions.SessionState{
435 User: sessionUser,
436 Email: sessionEmail,
437 },
438 expectedUser: sessionUser,
439 expectedEmail: sessionEmail,
440 },
441 "Session is missing Email and GetEmailAddress is implemented": {
442 session: &sessions.SessionState{
443 User: sessionUser,
444 },
445 expectedUser: sessionUser,
446 expectedEmail: providerEmail,
447 },
448 "Session is missing User and GetUserName is not implemented": {
449 session: &sessions.SessionState{
450 Email: sessionEmail,
451 },
452 expectedUser: "",
453 expectedEmail: sessionEmail,
454 },
455 }
457 for name, tc := range testCases {
458 t.Run(name, func(t *testing.T) {
459 opts := baseTestOptions()
460 err := validation.Validate(opts)
461 assert.NoError(t, err)
463 // intentionally set after validation.Validate(opts) since it will clobber
464 // our TestProvider and call `providers.New` defaulting to `providers.GoogleProvider`
465 opts.SetProvider(NewTestProvider(&url.URL{Host: "www.example.com"}, providerEmail))
466 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
467 if err != nil {
468 t.Fatal(err)
469 }
471 err = proxy.enrichSessionState(context.Background(), tc.session)
472 assert.NoError(t, err)
473 assert.Equal(t, tc.expectedUser, tc.session.User)
474 assert.Equal(t, tc.expectedEmail, tc.session.Email)
475 })
476 }
479func TestBasicAuthPassword(t *testing.T) {
480 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
481 logger.Printf("%#v", r)
482 var payload string
483 switch r.URL.Path {
484 case "/oauth/token":
485 payload = `{"access_token": "my_auth_token"}`
486 default:
487 payload = r.Header.Get("Authorization")
488 if payload == "" {
489 payload = "No Authorization header found."
490 }
491 }
492 w.WriteHeader(200)
493 _, err := w.Write([]byte(payload))
494 if err != nil {
495 t.Fatal(err)
496 }
497 }))
499 basicAuthPassword := "This is a secure password"
500 opts := baseTestOptions()
501 opts.UpstreamServers = options.Upstreams{
502 {
503 ID: providerServer.URL,
504 Path: "/",
505 URI: providerServer.URL,
506 },
507 }
509 opts.Cookie.Secure = false
510 opts.InjectRequestHeaders = []options.Header{
511 {
512 Name: "Authorization",
513 Values: []options.HeaderValue{
514 {
515 ClaimSource: &options.ClaimSource{
516 Claim: "email",
517 BasicAuthPassword: &options.SecretSource{
518 Value: []byte(base64.StdEncoding.EncodeToString([]byte(basicAuthPassword))),
519 },
520 },
521 },
522 },
523 },
524 }
526 err := validation.Validate(opts)
527 assert.NoError(t, err)
529 providerURL, _ := url.Parse(providerServer.URL)
530 const emailAddress = "john.doe@example.com"
532 opts.SetProvider(NewTestProvider(providerURL, emailAddress))
533 proxy, err := NewOAuthProxy(opts, func(email string) bool {
534 return email == emailAddress
535 })
536 if err != nil {
537 t.Fatal(err)
538 }
540 // Save the required session
541 rw := httptest.NewRecorder()
542 req, _ := http.NewRequest("GET", "/", nil)
543 err = proxy.sessionStore.Save(rw, req, &sessions.SessionState{
544 Email: emailAddress,
545 })
546 assert.NoError(t, err)
548 // Extract the cookie value to inject into the test request
549 cookie := rw.Header().Values("Set-Cookie")[0]
551 req, _ = http.NewRequest("GET", "/", nil)
552 req.Header.Set("Cookie", cookie)
553 rw = httptest.NewRecorder()
554 proxy.ServeHTTP(rw, req)
556 // The username in the basic auth credentials is expected to be equal to the email address from the
557 // auth response, so we use the same variable here.
558 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+basicAuthPassword))
559 assert.Equal(t, expectedHeader, rw.Body.String())
560 providerServer.Close()
563func TestPassGroupsHeadersWithGroups(t *testing.T) {
564 opts := baseTestOptions()
565 opts.InjectRequestHeaders = []options.Header{
566 {
567 Name: "X-Forwarded-Groups",
568 Values: []options.HeaderValue{
569 {
570 ClaimSource: &options.ClaimSource{
571 Claim: "groups",
572 },
573 },
574 },
575 },
576 }
578 err := validation.Validate(opts)
579 assert.NoError(t, err)
581 const emailAddress = "john.doe@example.com"
582 const userName = "9fcab5c9b889a557"
584 groups := []string{"a", "b"}
585 created := time.Now()
586 session := &sessions.SessionState{
587 User: userName,
588 Groups: groups,
589 Email: emailAddress,
590 AccessToken: "oauth_token",
591 CreatedAt: &created,
592 }
594 proxy, err := NewOAuthProxy(opts, func(email string) bool {
595 return email == emailAddress
596 })
597 assert.NoError(t, err)
599 // Save the required session
600 rw := httptest.NewRecorder()
601 req, _ := http.NewRequest("GET", "/", nil)
602 err = proxy.sessionStore.Save(rw, req, session)
603 assert.NoError(t, err)
605 // Extract the cookie value to inject into the test request
606 cookie := rw.Header().Values("Set-Cookie")[0]
608 req, _ = http.NewRequest("GET", "/", nil)
609 req.Header.Set("Cookie", cookie)
610 rw = httptest.NewRecorder()
611 proxy.ServeHTTP(rw, req)
613 assert.Equal(t, groups, req.Header["X-Forwarded-Groups"])
616type PassAccessTokenTest struct {
617 providerServer *httptest.Server
618 proxy *OAuthProxy
619 opts *options.Options
622type PassAccessTokenTestOptions struct {
623 PassAccessToken bool
624 ProxyUpstream options.Upstream
627func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTest, error) {
628 patt := &PassAccessTokenTest{}
630 patt.providerServer = httptest.NewServer(
631 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
632 var payload string
633 switch r.URL.Path {
634 case "/oauth/token":
635 payload = `{"access_token": "my_auth_token"}`
636 default:
637 payload = r.Header.Get("X-Forwarded-Access-Token")
638 if payload == "" {
639 payload = "No access token found."
640 }
641 }
642 w.WriteHeader(200)
643 _, err := w.Write([]byte(payload))
644 if err != nil {
645 panic(err)
646 }
647 }))
649 patt.opts = baseTestOptions()
650 patt.opts.UpstreamServers = options.Upstreams{
651 {
652 ID: patt.providerServer.URL,
653 Path: "/",
654 URI: patt.providerServer.URL,
655 },
656 }
657 if opts.ProxyUpstream.ID != "" {
658 patt.opts.UpstreamServers = append(patt.opts.UpstreamServers, opts.ProxyUpstream)
659 }
661 patt.opts.Cookie.Secure = false
662 if opts.PassAccessToken {
663 patt.opts.InjectRequestHeaders = []options.Header{
664 {
665 Name: "X-Forwarded-Access-Token",
666 Values: []options.HeaderValue{
667 {
668 ClaimSource: &options.ClaimSource{
669 Claim: "access_token",
670 },
671 },
672 },
673 },
674 }
675 }
677 err := validation.Validate(patt.opts)
678 if err != nil {
679 return nil, err
680 }
682 providerURL, _ := url.Parse(patt.providerServer.URL)
683 const emailAddress = "michael.bland@gsa.gov"
685 patt.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
686 patt.proxy, err = NewOAuthProxy(patt.opts, func(email string) bool {
687 return email == emailAddress
688 })
689 if err != nil {
690 return nil, err
691 }
692 return patt, nil
695func (patTest *PassAccessTokenTest) Close() {
696 patTest.providerServer.Close()
699func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
700 cookie string) {
701 rw := httptest.NewRecorder()
702 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
703 strings.NewReader(""))
704 if err != nil {
705 return 0, ""
706 }
707 req.AddCookie(patTest.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
708 patTest.proxy.ServeHTTP(rw, req)
709 return rw.Code, rw.Header().Values("Set-Cookie")[1]
712// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
713// and cookie and returns body and status code.
714func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
715 cookieName := patTest.proxy.CookieName
716 var value string
717 keyPrefix := cookieName + "="
719 for _, field := range strings.Split(cookie, "; ") {
720 value = strings.TrimPrefix(field, keyPrefix)
721 if value != field {
722 break
723 } else {
724 value = ""
725 }
726 }
727 if value == "" {
728 return 0, ""
729 }
731 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
732 if err != nil {
733 return 0, ""
734 }
735 req.AddCookie(&http.Cookie{
736 Name: cookieName,
737 Value: value,
738 Path: "/",
739 Expires: time.Now().Add(time.Duration(24)),
740 HttpOnly: true,
741 })
743 rw := httptest.NewRecorder()
744 patTest.proxy.ServeHTTP(rw, req)
745 return rw.Code, rw.Body.String()
748func TestForwardAccessTokenUpstream(t *testing.T) {
749 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
750 PassAccessToken: true,
751 })
752 if err != nil {
753 t.Fatal(err)
754 }
755 t.Cleanup(patTest.Close)
757 // A successful validation will redirect and set the auth cookie.
758 code, cookie := patTest.getCallbackEndpoint()
759 if code != 302 {
760 t.Fatalf("expected 302; got %d", code)
761 }
762 assert.NotNil(t, cookie)
764 // Now we make a regular request; the access_token from the cookie is
765 // forwarded as the "X-Forwarded-Access-Token" header. The token is
766 // read by the test provider server and written in the response body.
767 code, payload := patTest.getEndpointWithCookie(cookie, "/")
768 if code != 200 {
769 t.Fatalf("expected 200; got %d", code)
770 }
771 assert.Equal(t, "my_auth_token", payload)
774func TestStaticProxyUpstream(t *testing.T) {
775 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
776 PassAccessToken: true,
777 ProxyUpstream: options.Upstream{
778 ID: "static-proxy",
779 Path: "/static-proxy",
780 Static: true,
781 },
782 })
783 if err != nil {
784 t.Fatal(err)
785 }
786 t.Cleanup(patTest.Close)
788 // A successful validation will redirect and set the auth cookie.
789 code, cookie := patTest.getCallbackEndpoint()
790 if code != 302 {
791 t.Fatalf("expected 302; got %d", code)
792 }
793 assert.NotEqual(t, nil, cookie)
795 // Now we make a regular request against the upstream proxy; And validate
796 // the returned status code through the static proxy.
797 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
798 if code != 200 {
799 t.Fatalf("expected 200; got %d", code)
800 }
801 assert.Equal(t, "Authenticated", payload)
804func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
805 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
806 PassAccessToken: false,
807 })
808 if err != nil {
809 t.Fatal(err)
810 }
811 t.Cleanup(patTest.Close)
813 // A successful validation will redirect and set the auth cookie.
814 code, cookie := patTest.getCallbackEndpoint()
815 if code != 302 {
816 t.Fatalf("expected 302; got %d", code)
817 }
818 assert.NotEqual(t, nil, cookie)
820 // Now we make a regular request, but the access token header should
821 // not be present.
822 code, payload := patTest.getEndpointWithCookie(cookie, "/")
823 if code != 200 {
824 t.Fatalf("expected 200; got %d", code)
825 }
826 assert.Equal(t, "No access token found.", payload)
829type SignInPageTest struct {
830 opts *options.Options
831 proxy *OAuthProxy
832 signInRegexp *regexp.Regexp
833 signInProviderRegexp *regexp.Regexp
836const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
837const signInSkipProvider = `>Found<`
839func NewSignInPageTest(skipProvider bool) (*SignInPageTest, error) {
840 var sipTest SignInPageTest
842 sipTest.opts = baseTestOptions()
843 sipTest.opts.SkipProviderButton = skipProvider
844 err := validation.Validate(sipTest.opts)
845 if err != nil {
846 return nil, err
847 }
849 sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
850 return true
851 })
852 if err != nil {
853 return nil, err
854 }
855 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
856 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
858 return &sipTest, nil
861func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
862 rw := httptest.NewRecorder()
863 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
864 sipTest.proxy.ServeHTTP(rw, req)
865 return rw.Code, rw.Body.String()
868func TestSignInPageIncludesTargetRedirect(t *testing.T) {
869 sipTest, err := NewSignInPageTest(false)
870 if err != nil {
871 t.Fatal(err)
872 }
873 const endpoint = "/some/random/endpoint"
875 code, body := sipTest.GetEndpoint(endpoint)
876 assert.Equal(t, 403, code)
878 match := sipTest.signInRegexp.FindStringSubmatch(body)
879 if match == nil {
880 t.Fatal("Did not find pattern in body: " +
881 signInRedirectPattern + "\nBody:\n" + body)
882 }
883 if match[1] != endpoint {
884 t.Fatal(`expected redirect to "` + endpoint +
885 `", but was "` + match[1] + `"`)
886 }
889func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
890 sipTest, err := NewSignInPageTest(false)
891 if err != nil {
892 t.Fatal(err)
893 }
894 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
895 assert.Equal(t, 200, code)
897 match := sipTest.signInRegexp.FindStringSubmatch(body)
898 if match == nil {
899 t.Fatal("Did not find pattern in body: " +
900 signInRedirectPattern + "\nBody:\n" + body)
901 }
902 if match[1] != "/" {
903 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
904 }
907func TestSignInPageSkipProvider(t *testing.T) {
908 sipTest, err := NewSignInPageTest(true)
909 if err != nil {
910 t.Fatal(err)
911 }
913 endpoint := "/some/random/endpoint"
915 code, body := sipTest.GetEndpoint(endpoint)
916 assert.Equal(t, 302, code)
918 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
919 if match == nil {
920 t.Fatal("Did not find pattern in body: " +
921 signInSkipProvider + "\nBody:\n" + body)
922 }
925func TestSignInPageSkipProviderDirect(t *testing.T) {
926 sipTest, err := NewSignInPageTest(true)
927 if err != nil {
928 t.Fatal(err)
929 }
931 endpoint := "/sign_in"
933 code, body := sipTest.GetEndpoint(endpoint)
934 assert.Equal(t, 302, code)
936 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
937 if match == nil {
938 t.Fatal("Did not find pattern in body: " +
939 signInSkipProvider + "\nBody:\n" + body)
940 }
943type ProcessCookieTest struct {
944 opts *options.Options
945 proxy *OAuthProxy
946 rw *httptest.ResponseRecorder
947 req *http.Request
948 validateUser bool
951type ProcessCookieTestOpts struct {
952 providerValidateCookieResponse bool
955type OptionsModifier func(*options.Options)
957func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
958 var pcTest ProcessCookieTest
960 pcTest.opts = baseTestOptions()
961 for _, modifier := range modifiers {
962 modifier(pcTest.opts)
963 }
964 // First, set the CookieRefresh option so proxy.AesCipher is created,
965 // needed to encrypt the access_token.
966 pcTest.opts.Cookie.Refresh = time.Hour
967 err := validation.Validate(pcTest.opts)
968 if err != nil {
969 return nil, err
970 }
972 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
973 return pcTest.validateUser
974 })
975 if err != nil {
976 return nil, err
977 }
978 pcTest.proxy.provider = &TestProvider{
979 ValidToken: opts.providerValidateCookieResponse,
980 }
982 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
983 // access_token validation.
984 pcTest.proxy.CookieRefresh = time.Duration(0)
985 pcTest.rw = httptest.NewRecorder()
986 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
987 pcTest.validateUser = true
988 return &pcTest, nil
991func NewProcessCookieTestWithDefaults() (*ProcessCookieTest, error) {
992 return NewProcessCookieTest(ProcessCookieTestOpts{
993 providerValidateCookieResponse: true,
994 })
997func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
998 return NewProcessCookieTest(ProcessCookieTestOpts{
999 providerValidateCookieResponse: true,
1000 }, modifiers...)
1003func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
1004 err := p.proxy.SaveSession(p.rw, p.req, s)
1005 if err != nil {
1006 return err
1008 for _, cookie := range p.rw.Result().Cookies() {
1009 p.req.AddCookie(cookie)
1011 return nil
1014func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
1015 return p.proxy.LoadCookiedSession(p.req)
1018func TestLoadCookiedSession(t *testing.T) {
1019 pcTest, err := NewProcessCookieTestWithDefaults()
1020 if err != nil {
1021 t.Fatal(err)
1024 created := time.Now()
1025 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
1026 err = pcTest.SaveSession(startSession)
1027 assert.NoError(t, err)
1029 session, err := pcTest.LoadCookiedSession()
1030 if err != nil {
1031 t.Fatal(err)
1033 assert.Equal(t, startSession.Email, session.Email)
1034 assert.Equal(t, "", session.User)
1035 assert.Equal(t, startSession.AccessToken, session.AccessToken)
1038func TestProcessCookieNoCookieError(t *testing.T) {
1039 pcTest, err := NewProcessCookieTestWithDefaults()
1040 if err != nil {
1041 t.Fatal(err)
1044 session, err := pcTest.LoadCookiedSession()
1045 assert.Error(t, err, "cookie \"_oauth2_proxy\" not present")
1046 if session != nil {
1047 t.Errorf("expected nil session. got %#v", session)
1051func TestProcessCookieRefreshNotSet(t *testing.T) {
1052 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1053 opts.Cookie.Expire = time.Duration(23) * time.Hour
1054 })
1055 if err != nil {
1056 t.Fatal(err)
1059 reference := time.Now().Add(time.Duration(-2) * time.Hour)
1061 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1062 err = pcTest.SaveSession(startSession)
1063 assert.NoError(t, err)
1065 session, err := pcTest.LoadCookiedSession()
1066 assert.Equal(t, nil, err)
1067 if session.Age() < time.Duration(-2)*time.Hour {
1068 t.Errorf("cookie too young %v", session.Age())
1070 assert.Equal(t, startSession.Email, session.Email)
1073func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1074 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1075 opts.Cookie.Expire = time.Duration(24) * time.Hour
1076 })
1077 if err != nil {
1078 t.Fatal(err)
1081 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1082 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1083 err = pcTest.SaveSession(startSession)
1084 assert.NoError(t, err)
1086 session, err := pcTest.LoadCookiedSession()
1087 assert.NotEqual(t, nil, err)
1088 if session != nil {
1089 t.Errorf("expected nil session %#v", session)
1093func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1094 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1095 opts.Cookie.Expire = time.Duration(24) * time.Hour
1096 })
1097 if err != nil {
1098 t.Fatal(err)
1101 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1102 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1103 err = pcTest.SaveSession(startSession)
1104 assert.NoError(t, err)
1106 pcTest.proxy.CookieRefresh = time.Hour
1107 session, err := pcTest.LoadCookiedSession()
1108 assert.NotEqual(t, nil, err)
1109 if session != nil {
1110 t.Errorf("expected nil session %#v", session)
1114func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
1115 pcTest, err := NewProcessCookieTestWithDefaults()
1116 if err != nil {
1117 return nil, err
1119 pcTest.req, _ = http.NewRequest("GET",
1120 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1121 return pcTest, nil
1124func TestUserInfoEndpointAccepted(t *testing.T) {
1125 test, err := NewUserInfoEndpointTest()
1126 if err != nil {
1127 t.Fatal(err)
1130 startSession := &sessions.SessionState{
1131 Email: "john.doe@example.com", AccessToken: "my_access_token"}
1132 err = test.SaveSession(startSession)
1133 assert.NoError(t, err)
1135 test.proxy.ServeHTTP(test.rw, test.req)
1136 assert.Equal(t, http.StatusOK, test.rw.Code)
1137 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1138 assert.Equal(t, "{\"email\":\"john.doe@example.com\"}\n", string(bodyBytes))
1141func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1142 test, err := NewUserInfoEndpointTest()
1143 if err != nil {
1144 t.Fatal(err)
1147 test.proxy.ServeHTTP(test.rw, test.req)
1148 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1151func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1152 pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1153 if err != nil {
1154 return nil, err
1156 pcTest.req, _ = http.NewRequest("GET",
1157 pcTest.opts.ProxyPrefix+"/auth", nil)
1158 return pcTest, nil
1161func TestAuthOnlyEndpointAccepted(t *testing.T) {
1162 test, err := NewAuthOnlyEndpointTest()
1163 if err != nil {
1164 t.Fatal(err)
1167 created := time.Now()
1168 startSession := &sessions.SessionState{
1169 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1170 err = test.SaveSession(startSession)
1171 assert.NoError(t, err)
1173 test.proxy.ServeHTTP(test.rw, test.req)
1174 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1175 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1176 assert.Equal(t, "", string(bodyBytes))
1179func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1180 test, err := NewAuthOnlyEndpointTest()
1181 if err != nil {
1182 t.Fatal(err)
1185 test.proxy.ServeHTTP(test.rw, test.req)
1186 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1187 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1188 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1191func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1192 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1193 opts.Cookie.Expire = time.Duration(24) * time.Hour
1194 })
1195 if err != nil {
1196 t.Fatal(err)
1199 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1200 startSession := &sessions.SessionState{
1201 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1202 err = test.SaveSession(startSession)
1203 assert.NoError(t, err)
1205 test.proxy.ServeHTTP(test.rw, test.req)
1206 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1207 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1208 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1211func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1212 test, err := NewAuthOnlyEndpointTest()
1213 if err != nil {
1214 t.Fatal(err)
1217 created := time.Now()
1218 startSession := &sessions.SessionState{
1219 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1220 err = test.SaveSession(startSession)
1221 assert.NoError(t, err)
1222 test.validateUser = false
1224 test.proxy.ServeHTTP(test.rw, test.req)
1225 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1226 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1227 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1230func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1231 var pcTest ProcessCookieTest
1233 pcTest.opts = baseTestOptions()
1234 pcTest.opts.InjectResponseHeaders = []options.Header{
1236 Name: "X-Auth-Request-User",
1237 Values: []options.HeaderValue{
1239 ClaimSource: &options.ClaimSource{
1240 Claim: "user",
1241 },
1242 },
1243 },
1244 },
1246 Name: "X-Auth-Request-Email",
1247 Values: []options.HeaderValue{
1249 ClaimSource: &options.ClaimSource{
1250 Claim: "email",
1251 },
1252 },
1253 },
1254 },
1256 Name: "X-Auth-Request-Groups",
1257 Values: []options.HeaderValue{
1259 ClaimSource: &options.ClaimSource{
1260 Claim: "groups",
1261 },
1262 },
1263 },
1264 },
1266 Name: "X-Forwarded-Preferred-Username",
1267 Values: []options.HeaderValue{
1269 ClaimSource: &options.ClaimSource{
1270 Claim: "preferred_username",
1271 },
1272 },
1273 },
1274 },
1276 pcTest.opts.AllowedGroups = []string{"oauth_groups"}
1277 err := validation.Validate(pcTest.opts)
1278 assert.NoError(t, err)
1280 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1281 return pcTest.validateUser
1282 })
1283 if err != nil {
1284 t.Fatal(err)
1286 pcTest.proxy.provider = &TestProvider{
1287 ValidToken: true,
1290 pcTest.validateUser = true
1292 pcTest.rw = httptest.NewRecorder()
1293 pcTest.req, _ = http.NewRequest("GET",
1294 pcTest.opts.ProxyPrefix+"/auth", nil)
1296 created := time.Now()
1297 startSession := &sessions.SessionState{
1298 User: "oauth_user", Groups: []string{"oauth_groups"}, Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1299 err = pcTest.SaveSession(startSession)
1300 assert.NoError(t, err)
1302 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1303 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1304 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1305 assert.Equal(t, startSession.Groups, pcTest.rw.Header().Values("X-Auth-Request-Groups"))
1306 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1309func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1310 var pcTest ProcessCookieTest
1312 pcTest.opts = baseTestOptions()
1313 pcTest.opts.InjectResponseHeaders = []options.Header{
1315 Name: "X-Auth-Request-User",
1316 Values: []options.HeaderValue{
1318 ClaimSource: &options.ClaimSource{
1319 Claim: "user",
1320 },
1321 },
1322 },
1323 },
1325 Name: "X-Auth-Request-Email",
1326 Values: []options.HeaderValue{
1328 ClaimSource: &options.ClaimSource{
1329 Claim: "email",
1330 },
1331 },
1332 },
1333 },
1335 Name: "X-Auth-Request-Groups",
1336 Values: []options.HeaderValue{
1338 ClaimSource: &options.ClaimSource{
1339 Claim: "groups",
1340 },
1341 },
1342 },
1343 },
1345 Name: "X-Forwarded-Preferred-Username",
1346 Values: []options.HeaderValue{
1348 ClaimSource: &options.ClaimSource{
1349 Claim: "preferred_username",
1350 },
1351 },
1352 },
1353 },
1355 Name: "Authorization",
1356 Values: []options.HeaderValue{
1358 ClaimSource: &options.ClaimSource{
1359 Claim: "user",
1360 BasicAuthPassword: &options.SecretSource{
1361 Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
1362 },
1363 },
1364 },
1365 },
1366 },
1369 err := validation.Validate(pcTest.opts)
1370 assert.NoError(t, err)
1372 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1373 return pcTest.validateUser
1374 })
1375 if err != nil {
1376 t.Fatal(err)
1378 pcTest.proxy.provider = &TestProvider{
1379 ValidToken: true,
1382 pcTest.validateUser = true
1384 pcTest.rw = httptest.NewRecorder()
1385 pcTest.req, _ = http.NewRequest("GET",
1386 pcTest.opts.ProxyPrefix+"/auth", nil)
1388 created := time.Now()
1389 startSession := &sessions.SessionState{
1390 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1391 err = pcTest.SaveSession(startSession)
1392 assert.NoError(t, err)
1394 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1395 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1396 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1397 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1398 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:This is a secure password"))
1399 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1402func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1403 var pcTest ProcessCookieTest
1405 pcTest.opts = baseTestOptions()
1406 pcTest.opts.InjectResponseHeaders = []options.Header{
1408 Name: "X-Auth-Request-User",
1409 Values: []options.HeaderValue{
1411 ClaimSource: &options.ClaimSource{
1412 Claim: "user",
1413 },
1414 },
1415 },
1416 },
1418 Name: "X-Auth-Request-Email",
1419 Values: []options.HeaderValue{
1421 ClaimSource: &options.ClaimSource{
1422 Claim: "email",
1423 },
1424 },
1425 },
1426 },
1428 Name: "X-Auth-Request-Groups",
1429 Values: []options.HeaderValue{
1431 ClaimSource: &options.ClaimSource{
1432 Claim: "groups",
1433 },
1434 },
1435 },
1436 },
1438 Name: "X-Forwarded-Preferred-Username",
1439 Values: []options.HeaderValue{
1441 ClaimSource: &options.ClaimSource{
1442 Claim: "preferred_username",
1443 },
1444 },
1445 },
1446 },
1448 err := validation.Validate(pcTest.opts)
1449 assert.NoError(t, err)
1451 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1452 return pcTest.validateUser
1453 })
1454 if err != nil {
1455 t.Fatal(err)
1457 pcTest.proxy.provider = &TestProvider{
1458 ValidToken: true,
1461 pcTest.validateUser = true
1463 pcTest.rw = httptest.NewRecorder()
1464 pcTest.req, _ = http.NewRequest("GET",
1465 pcTest.opts.ProxyPrefix+"/auth", nil)
1467 created := time.Now()
1468 startSession := &sessions.SessionState{
1469 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1470 err = pcTest.SaveSession(startSession)
1471 assert.NoError(t, err)
1473 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1474 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1475 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1476 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1477 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1480func TestAuthSkippedForPreflightRequests(t *testing.T) {
1481 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1482 w.WriteHeader(200)
1483 _, err := w.Write([]byte("response"))
1484 if err != nil {
1485 t.Fatal(err)
1487 }))
1488 t.Cleanup(upstreamServer.Close)
1490 opts := baseTestOptions()
1491 opts.UpstreamServers = options.Upstreams{
1493 ID: upstreamServer.URL,
1494 Path: "/",
1495 URI: upstreamServer.URL,
1496 },
1498 opts.SkipAuthPreflight = true
1499 err := validation.Validate(opts)
1500 assert.NoError(t, err)
1502 upstreamURL, _ := url.Parse(upstreamServer.URL)
1503 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1505 proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
1506 if err != nil {
1507 t.Fatal(err)
1509 rw := httptest.NewRecorder()
1510 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1511 proxy.ServeHTTP(rw, req)
1513 assert.Equal(t, 200, rw.Code)
1514 assert.Equal(t, "response", rw.Body.String())
1517type SignatureAuthenticator struct {
1518 auth hmacauth.HmacAuth
1521func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1522 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1524 var msg string
1525 switch result {
1526 case hmacauth.ResultNoSignature:
1527 msg = "no signature received"
1528 case hmacauth.ResultMatch:
1529 msg = "signatures match"
1530 case hmacauth.ResultMismatch:
1531 msg = fmt.Sprintf(
1532 "signatures do not match:\n received: %s\n computed: %s",
1533 headerSig,
1534 computedSig)
1535 default:
1536 panic("unknown result value: " + result.String())
1539 _, err := w.Write([]byte(msg))
1540 if err != nil {
1541 panic(err)
1545type SignatureTest struct {
1546 opts *options.Options
1547 upstream *httptest.Server
1548 upstreamHost string
1549 provider *httptest.Server
1550 header http.Header
1551 rw *httptest.ResponseRecorder
1552 authenticator *SignatureAuthenticator
1555func NewSignatureTest() (*SignatureTest, error) {
1556 opts := baseTestOptions()
1557 opts.EmailDomains = []string{"acm.org"}
1559 authenticator := &SignatureAuthenticator{}
1560 upstreamServer := httptest.NewServer(
1561 http.HandlerFunc(authenticator.Authenticate))
1562 upstreamURL, err := url.Parse(upstreamServer.URL)
1563 if err != nil {
1564 return nil, err
1566 opts.UpstreamServers = options.Upstreams{
1568 ID: upstreamServer.URL,
1569 Path: "/",
1570 URI: upstreamServer.URL,
1571 },
1574 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1575 _, err := w.Write([]byte(`{"access_token": "my_auth_token"}`))
1576 if err != nil {
1577 panic(err)
1580 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1581 providerURL, err := url.Parse(provider.URL)
1582 if err != nil {
1583 return nil, err
1585 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1587 return &SignatureTest{
1588 opts,
1589 upstreamServer,
1590 upstreamURL.Host,
1591 provider,
1592 make(http.Header),
1593 httptest.NewRecorder(),
1594 authenticator,
1595 }, nil
1598func (st *SignatureTest) Close() {
1599 st.provider.Close()
1600 st.upstream.Close()
1603// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1604// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1605// https://github.com/18F/hmacauth/pull/4
1606type fakeNetConn struct {
1607 reqBody string
1610func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1611 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1612 copy(p, fnc.reqBody)
1613 fnc.reqBody = ""
1614 return bodyLen, io.EOF
1616 return 0, io.EOF
1619func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) error {
1620 err := validation.Validate(st.opts)
1621 if err != nil {
1622 return err
1624 proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
1625 if err != nil {
1626 return err
1629 var bodyBuf io.ReadCloser
1630 if body != "" {
1631 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1633 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1634 req.Header = st.header
1636 state := &sessions.SessionState{
1637 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1638 err = proxy.SaveSession(st.rw, req, state)
1639 if err != nil {
1640 return err
1642 for _, c := range st.rw.Result().Cookies() {
1643 req.AddCookie(c)
1645 // This is used by the upstream to validate the signature.
1646 st.authenticator.auth = hmacauth.NewHmacAuth(
1647 crypto.SHA1, []byte(key), upstream.SignatureHeader, upstream.SignatureHeaders)
1648 proxy.ServeHTTP(st.rw, req)
1650 return nil
1653func TestRequestSignature(t *testing.T) {
1654 testCases := map[string]struct {
1655 method string
1656 body string
1657 key string
1658 resp string
1659 }{
1660 "No request signature": {
1661 method: "GET",
1662 body: "",
1663 key: "",
1664 resp: "no signature received",
1665 },
1666 "Get request": {
1667 method: "GET",
1668 body: "",
1669 key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1670 resp: "signatures match",
1671 },
1672 "Post request": {
1673 method: "POST",
1674 body: `{ "hello": "world!" }`,
1675 key: "d90df39e2d19282840252612dd7c81421a372f61",
1676 resp: "signatures match",
1677 },
1679 for name, tc := range testCases {
1680 t.Run(name, func(t *testing.T) {
1681 st, err := NewSignatureTest()
1682 if err != nil {
1683 t.Fatal(err)
1685 t.Cleanup(st.Close)
1686 if tc.key != "" {
1687 st.opts.SignatureKey = fmt.Sprintf("sha1:%s", tc.key)
1689 err = st.MakeRequestWithExpectedKey(tc.method, tc.body, tc.key)
1690 assert.NoError(t, err)
1691 assert.Equal(t, 200, st.rw.Code)
1692 assert.Equal(t, tc.resp, st.rw.Body.String())
1693 })
1697func TestGetRedirect(t *testing.T) {
1698 opts := baseTestOptions()
1699 err := validation.Validate(opts)
1700 assert.NoError(t, err)
1701 require.NotEmpty(t, opts.ProxyPrefix)
1702 proxy, err := NewOAuthProxy(opts, func(s string) bool { return false })
1703 if err != nil {
1704 t.Fatal(err)
1707 tests := []struct {
1708 name string
1709 url string
1710 expectedRedirect string
1711 }{
1713 name: "request outside of ProxyPrefix redirects to original URL",
1714 url: "/foo/bar",
1715 expectedRedirect: "/foo/bar",
1716 },
1718 name: "request with query preserves query",
1719 url: "/foo?bar",
1720 expectedRedirect: "/foo?bar",
1721 },
1723 name: "request under ProxyPrefix redirects to root",
1724 url: proxy.ProxyPrefix + "/foo/bar",
1725 expectedRedirect: "/",
1726 },
1728 for _, tt := range tests {
1729 t.Run(tt.name, func(t *testing.T) {
1730 req, _ := http.NewRequest("GET", tt.url, nil)
1731 redirect, err := proxy.GetRedirect(req)
1733 assert.NoError(t, err)
1734 assert.Equal(t, tt.expectedRedirect, redirect)
1735 })
1739type ajaxRequestTest struct {
1740 opts *options.Options
1741 proxy *OAuthProxy
1744func newAjaxRequestTest() (*ajaxRequestTest, error) {
1745 test := &ajaxRequestTest{}
1746 test.opts = baseTestOptions()
1747 err := validation.Validate(test.opts)
1748 if err != nil {
1749 return nil, err
1752 test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
1753 return true
1754 })
1755 if err != nil {
1756 return nil, err
1758 return test, nil
1761func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1762 rw := httptest.NewRecorder()
1763 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1764 if err != nil {
1765 return 0, nil, err
1767 req.Header = header
1768 test.proxy.ServeHTTP(rw, req)
1769 return rw.Code, rw.Header(), nil
1772func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1773 test, err := newAjaxRequestTest()
1774 if err != nil {
1775 t.Fatal(err)
1777 endpoint := "/test"
1779 code, rh, err := test.getEndpoint(endpoint, header)
1780 assert.NoError(t, err)
1781 assert.Equal(t, http.StatusUnauthorized, code)
1782 mime := rh.Get("Content-Type")
1783 assert.Equal(t, applicationJSON, mime)
1785func TestAjaxUnauthorizedRequest1(t *testing.T) {
1786 header := make(http.Header)
1787 header.Add("accept", applicationJSON)
1789 testAjaxUnauthorizedRequest(t, header)
1792func TestAjaxUnauthorizedRequest2(t *testing.T) {
1793 header := make(http.Header)
1794 header.Add("Accept", applicationJSON)
1796 testAjaxUnauthorizedRequest(t, header)
1799func TestAjaxForbiddendRequest(t *testing.T) {
1800 test, err := newAjaxRequestTest()
1801 if err != nil {
1802 t.Fatal(err)
1804 endpoint := "/test"
1805 header := make(http.Header)
1806 code, rh, err := test.getEndpoint(endpoint, header)
1807 assert.NoError(t, err)
1808 assert.Equal(t, http.StatusForbidden, code)
1809 mime := rh.Get("Content-Type")
1810 assert.NotEqual(t, applicationJSON, mime)
1813func TestClearSplitCookie(t *testing.T) {
1814 opts := baseTestOptions()
1815 opts.Cookie.Secret = base64CookieSecret
1816 opts.Cookie.Name = "oauth2"
1817 opts.Cookie.Domains = []string{"abc"}
1818 err := validation.Validate(opts)
1819 assert.NoError(t, err)
1821 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1822 if err != nil {
1823 t.Fatal(err)
1826 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1827 var rw = httptest.NewRecorder()
1828 req := httptest.NewRequest("get", "/", nil)
1830 req.AddCookie(&http.Cookie{
1831 Name: "test1",
1832 Value: "test1",
1833 })
1834 req.AddCookie(&http.Cookie{
1835 Name: "oauth2_0",
1836 Value: "oauth2_0",
1837 })
1838 req.AddCookie(&http.Cookie{
1839 Name: "oauth2_1",
1840 Value: "oauth2_1",
1841 })
1843 err = p.ClearSessionCookie(rw, req)
1844 assert.NoError(t, err)
1845 header := rw.Header()
1847 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1850func TestClearSingleCookie(t *testing.T) {
1851 opts := baseTestOptions()
1852 opts.Cookie.Name = "oauth2"
1853 opts.Cookie.Domains = []string{"abc"}
1854 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1855 if err != nil {
1856 t.Fatal(err)
1859 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1860 var rw = httptest.NewRecorder()
1861 req := httptest.NewRequest("get", "/", nil)
1863 req.AddCookie(&http.Cookie{
1864 Name: "test1",
1865 Value: "test1",
1866 })
1867 req.AddCookie(&http.Cookie{
1868 Name: "oauth2",
1869 Value: "oauth2",
1870 })
1872 err = p.ClearSessionCookie(rw, req)
1873 assert.NoError(t, err)
1874 header := rw.Header()
1876 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1879type NoOpKeySet struct {
1882func (NoOpKeySet) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
1883 splitStrings := strings.Split(jwt, ".")
1884 payloadString := splitStrings[1]
1885 return base64.RawURLEncoding.DecodeString(payloadString)
1888func TestGetJwtSession(t *testing.T) {
1889 /* token payload:
1891 "sub": "1234567890",
1892 "aud": "https://test.myapp.com",
1893 "name": "John Doe",
1894 "email": "john@example.com",
1895 "iss": "https://issuer.example.com",
1896 "iat": 1553691215,
1897 "exp": 1912151821
1899 */
1900 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1901 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1902 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1903 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1904 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1905 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1907 keyset := NoOpKeySet{}
1908 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1909 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1911 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1912 opts.InjectRequestHeaders = []options.Header{
1914 Name: "Authorization",
1915 Values: []options.HeaderValue{
1917 ClaimSource: &options.ClaimSource{
1918 Claim: "id_token",
1919 Prefix: "Bearer ",
1920 },
1921 },
1922 },
1923 },
1925 Name: "X-Forwarded-User",
1926 Values: []options.HeaderValue{
1928 ClaimSource: &options.ClaimSource{
1929 Claim: "user",
1930 },
1931 },
1932 },
1933 },
1935 Name: "X-Forwarded-Email",
1936 Values: []options.HeaderValue{
1938 ClaimSource: &options.ClaimSource{
1939 Claim: "email",
1940 },
1941 },
1942 },
1943 },
1946 opts.InjectResponseHeaders = []options.Header{
1948 Name: "Authorization",
1949 Values: []options.HeaderValue{
1951 ClaimSource: &options.ClaimSource{
1952 Claim: "id_token",
1953 Prefix: "Bearer ",
1954 },
1955 },
1956 },
1957 },
1959 Name: "X-Auth-Request-User",
1960 Values: []options.HeaderValue{
1962 ClaimSource: &options.ClaimSource{
1963 Claim: "user",
1964 },
1965 },
1966 },
1967 },
1969 Name: "X-Auth-Request-Email",
1970 Values: []options.HeaderValue{
1972 ClaimSource: &options.ClaimSource{
1973 Claim: "email",
1974 },
1975 },
1976 },
1977 },
1980 opts.SkipJwtBearerTokens = true
1981 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
1982 })
1983 if err != nil {
1984 t.Fatal(err)
1986 tp, _ := test.proxy.provider.(*TestProvider)
1987 tp.GroupValidator = func(s string) bool {
1988 return true
1991 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1992 test.req.Header = map[string][]string{
1993 "Authorization": {authHeader},
1996 test.proxy.ServeHTTP(test.rw, test.req)
1997 if test.rw.Code >= 400 {
1998 t.Fatalf("expected 3xx got %d", test.rw.Code)
2001 // Check PassAuthorization, should overwrite Basic header
2002 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
2003 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "1234567890")
2004 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
2006 // SetAuthorization and SetXAuthRequest
2007 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
2008 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "1234567890")
2009 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
2012func Test_prepareNoCache(t *testing.T) {
2013 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2014 prepareNoCache(w)
2015 })
2016 mux := http.NewServeMux()
2017 mux.Handle("/", handler)
2019 rec := httptest.NewRecorder()
2020 req := httptest.NewRequest(http.MethodGet, "/", nil)
2021 mux.ServeHTTP(rec, req)
2023 for k, v := range noCacheHeaders {
2024 assert.Equal(t, rec.Header().Get(k), v)
2028func Test_noCacheHeaders(t *testing.T) {
2029 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2030 _, err := w.Write([]byte("upstream"))
2031 if err != nil {
2032 t.Error(err)
2034 }))
2035 t.Cleanup(upstreamServer.Close)
2037 opts := baseTestOptions()
2038 opts.UpstreamServers = options.Upstreams{
2040 ID: upstreamServer.URL,
2041 Path: "/",
2042 URI: upstreamServer.URL,
2043 },
2045 opts.SkipAuthRegex = []string{".*"}
2046 err := validation.Validate(opts)
2047 assert.NoError(t, err)
2048 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2049 if err != nil {
2050 t.Fatal(err)
2053 t.Run("not exist in response from upstream", func(t *testing.T) {
2054 rec := httptest.NewRecorder()
2055 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
2056 proxy.ServeHTTP(rec, req)
2058 assert.Equal(t, http.StatusOK, rec.Code)
2059 assert.Equal(t, "upstream", rec.Body.String())
2061 // checking noCacheHeaders does not exists in response headers from upstream
2062 for k := range noCacheHeaders {
2063 assert.Equal(t, "", rec.Header().Get(k))
2065 })
2067 t.Run("has no-cache", func(t *testing.T) {
2068 tests := []struct {
2069 path string
2070 hasNoCache bool
2071 }{
2073 path: "/oauth2/sign_in",
2074 hasNoCache: true,
2075 },
2077 path: "/oauth2/sign_out",
2078 hasNoCache: true,
2079 },
2081 path: "/oauth2/start",
2082 hasNoCache: true,
2083 },
2085 path: "/oauth2/callback",
2086 hasNoCache: true,
2087 },
2089 path: "/oauth2/auth",
2090 hasNoCache: false,
2091 },
2093 path: "/oauth2/userinfo",
2094 hasNoCache: true,
2095 },
2097 path: "/upstream",
2098 hasNoCache: false,
2099 },
2102 for _, tt := range tests {
2103 t.Run(tt.path, func(t *testing.T) {
2104 rec := httptest.NewRecorder()
2105 req := httptest.NewRequest(http.MethodGet, tt.path, nil)
2106 proxy.ServeHTTP(rec, req)
2107 cacheControl := rec.Result().Header.Get("Cache-Control")
2108 if tt.hasNoCache != (strings.Contains(cacheControl, "no-cache")) {
2109 t.Errorf(`unexpected "Cache-Control" header: %s`, cacheControl)
2111 })
2114 })
2117func baseTestOptions() *options.Options {
2118 opts := options.NewOptions()
2119 opts.Cookie.Secret = rawCookieSecret
2120 opts.ClientID = clientID
2121 opts.ClientSecret = clientSecret
2122 opts.EmailDomains = []string{"*"}
2124 // Default injected headers for legacy configuration
2125 opts.InjectRequestHeaders = []options.Header{
2127 Name: "Authorization",
2128 Values: []options.HeaderValue{
2130 ClaimSource: &options.ClaimSource{
2131 Claim: "user",
2132 BasicAuthPassword: &options.SecretSource{
2133 Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
2134 },
2135 },
2136 },
2137 },
2138 },
2140 Name: "X-Forwarded-User",
2141 Values: []options.HeaderValue{
2143 ClaimSource: &options.ClaimSource{
2144 Claim: "user",
2145 },
2146 },
2147 },
2148 },
2150 Name: "X-Forwarded-Email",
2151 Values: []options.HeaderValue{
2153 ClaimSource: &options.ClaimSource{
2154 Claim: "email",
2155 },
2156 },
2157 },
2158 },
2160 return opts
2163func TestTrustedIPs(t *testing.T) {
2164 tests := []struct {
2165 name string
2166 trustedIPs []string
2167 reverseProxy bool
2168 realClientIPHeader string
2169 req *http.Request
2170 expectTrusted bool
2171 }{
2172 // Check unconfigured behavior.
2174 name: "Default",
2175 trustedIPs: nil,
2176 reverseProxy: false,
2177 realClientIPHeader: "X-Real-IP", // Default value
2178 req: func() *http.Request {
2179 req, _ := http.NewRequest("GET", "/", nil)
2180 return req
2181 }(),
2182 expectTrusted: false,
2183 },
2184 // Check using req.RemoteAddr (Options.ReverseProxy == false).
2186 name: "WithRemoteAddr",
2187 trustedIPs: []string{"127.0.0.1"},
2188 reverseProxy: false,
2189 realClientIPHeader: "X-Real-IP", // Default value
2190 req: func() *http.Request {
2191 req, _ := http.NewRequest("GET", "/", nil)
2192 req.RemoteAddr = "127.0.0.1:43670"
2193 return req
2194 }(),
2195 expectTrusted: true,
2196 },
2197 // Check ignores req.RemoteAddr match when behind a reverse proxy / missing header.
2199 name: "IgnoresRemoteAddrInReverseProxyMode",
2200 trustedIPs: []string{"127.0.0.1"},
2201 reverseProxy: true,
2202 realClientIPHeader: "X-Real-IP", // Default value
2203 req: func() *http.Request {
2204 req, _ := http.NewRequest("GET", "/", nil)
2205 req.RemoteAddr = "127.0.0.1:44324"
2206 return req
2207 }(),
2208 expectTrusted: false,
2209 },
2210 // Check successful trusting of localhost in IPv4.
2212 name: "TrustsLocalhostInReverseProxyMode",
2213 trustedIPs: []string{"127.0.0.0/8", "::1"},
2214 reverseProxy: true,
2215 realClientIPHeader: "X-Forwarded-For",
2216 req: func() *http.Request {
2217 req, _ := http.NewRequest("GET", "/", nil)
2218 req.Header.Add("X-Forwarded-For", "127.0.0.1")
2219 return req
2220 }(),
2221 expectTrusted: true,
2222 },
2223 // Check successful trusting of localhost in IPv6.
2225 name: "TrustsIP6LocalostInReverseProxyMode",
2226 trustedIPs: []string{"127.0.0.0/8", "::1"},
2227 reverseProxy: true,
2228 realClientIPHeader: "X-Forwarded-For",
2229 req: func() *http.Request {
2230 req, _ := http.NewRequest("GET", "/", nil)
2231 req.Header.Add("X-Forwarded-For", "::1")
2232 return req
2233 }(),
2234 expectTrusted: true,
2235 },
2236 // Check does not trust random IPv4 address.
2238 name: "DoesNotTrustRandomIP4Address",
2239 trustedIPs: []string{"127.0.0.0/8", "::1"},
2240 reverseProxy: true,
2241 realClientIPHeader: "X-Forwarded-For",
2242 req: func() *http.Request {
2243 req, _ := http.NewRequest("GET", "/", nil)
2244 req.Header.Add("X-Forwarded-For", "12.34.56.78")
2245 return req
2246 }(),
2247 expectTrusted: false,
2248 },
2249 // Check does not trust random IPv6 address.
2251 name: "DoesNotTrustRandomIP6Address",
2252 trustedIPs: []string{"127.0.0.0/8", "::1"},
2253 reverseProxy: true,
2254 realClientIPHeader: "X-Forwarded-For",
2255 req: func() *http.Request {
2256 req, _ := http.NewRequest("GET", "/", nil)
2257 req.Header.Add("X-Forwarded-For", "::2")
2258 return req
2259 }(),
2260 expectTrusted: false,
2261 },
2262 // Check respects correct header.
2264 name: "RespectsCorrectHeaderInReverseProxyMode",
2265 trustedIPs: []string{"127.0.0.0/8", "::1"},
2266 reverseProxy: true,
2267 realClientIPHeader: "X-Forwarded-For",
2268 req: func() *http.Request {
2269 req, _ := http.NewRequest("GET", "/", nil)
2270 req.Header.Add("X-Real-IP", "::1")
2271 return req
2272 }(),
2273 expectTrusted: false,
2274 },
2275 // Check doesn't trust if garbage is provided.
2277 name: "DoesNotTrustGarbageInReverseProxyMode",
2278 trustedIPs: []string{"127.0.0.0/8", "::1"},
2279 reverseProxy: true,
2280 realClientIPHeader: "X-Forwarded-For",
2281 req: func() *http.Request {
2282 req, _ := http.NewRequest("GET", "/", nil)
2283 req.Header.Add("X-Forwarded-For", "adsfljk29242as!!")
2284 return req
2285 }(),
2286 expectTrusted: false,
2287 },
2288 // Check doesn't trust if garbage is provided (no reverse-proxy).
2290 name: "DoesNotTrustGarbage",
2291 trustedIPs: []string{"127.0.0.0/8", "::1"},
2292 reverseProxy: false,
2293 realClientIPHeader: "X-Real-IP",
2294 req: func() *http.Request {
2295 req, _ := http.NewRequest("GET", "/", nil)
2296 req.RemoteAddr = "adsfljk29242as!!"
2297 return req
2298 }(),
2299 expectTrusted: false,
2300 },
2303 for _, tt := range tests {
2304 t.Run(tt.name, func(t *testing.T) {
2305 opts := baseTestOptions()
2306 opts.UpstreamServers = options.Upstreams{
2308 ID: "static",
2309 Path: "/",
2310 Static: true,
2311 },
2313 opts.TrustedIPs = tt.trustedIPs
2314 opts.ReverseProxy = tt.reverseProxy
2315 opts.RealClientIPHeader = tt.realClientIPHeader
2316 err := validation.Validate(opts)
2317 assert.NoError(t, err)
2319 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
2320 assert.NoError(t, err)
2321 rw := httptest.NewRecorder()
2323 proxy.ServeHTTP(rw, tt.req)
2324 if tt.expectTrusted {
2325 assert.Equal(t, 200, rw.Code)
2326 } else {
2327 assert.Equal(t, 403, rw.Code)
2329 })
2333func Test_buildRoutesAllowlist(t *testing.T) {
2334 type expectedAllowedRoute struct {
2335 method string
2336 regexString string
2339 testCases := []struct {
2340 name string
2341 skipAuthRegex []string
2342 skipAuthRoutes []string
2343 expectedRoutes []expectedAllowedRoute
2344 shouldError bool
2345 }{
2347 name: "No skip auth configured",
2348 skipAuthRegex: []string{},
2349 skipAuthRoutes: []string{},
2350 expectedRoutes: []expectedAllowedRoute{},
2351 shouldError: false,
2352 },
2354 name: "Only skipAuthRegex configured",
2355 skipAuthRegex: []string{
2356 "^/foo/bar",
2357 "^/baz/[0-9]+/thing",
2358 },
2359 skipAuthRoutes: []string{},
2360 expectedRoutes: []expectedAllowedRoute{
2362 method: "",
2363 regexString: "^/foo/bar",
2364 },
2366 method: "",
2367 regexString: "^/baz/[0-9]+/thing",
2368 },
2369 },
2370 shouldError: false,
2371 },
2373 name: "Only skipAuthRoutes configured",
2374 skipAuthRegex: []string{},
2375 skipAuthRoutes: []string{
2376 "GET=^/foo/bar",
2377 "POST=^/baz/[0-9]+/thing",
2378 "^/all/methods$",
2379 "WEIRD=^/methods/are/allowed",
2380 "PATCH=/second/equals?are=handled&just=fine",
2381 },
2382 expectedRoutes: []expectedAllowedRoute{
2384 method: "GET",
2385 regexString: "^/foo/bar",
2386 },
2388 method: "POST",
2389 regexString: "^/baz/[0-9]+/thing",
2390 },
2392 method: "",
2393 regexString: "^/all/methods$",
2394 },
2396 method: "WEIRD",
2397 regexString: "^/methods/are/allowed",
2398 },
2400 method: "PATCH",
2401 regexString: "/second/equals?are=handled&just=fine",
2402 },
2403 },
2404 shouldError: false,
2405 },
2407 name: "Both skipAuthRegexes and skipAuthRoutes configured",
2408 skipAuthRegex: []string{
2409 "^/foo/bar/regex",
2410 "^/baz/[0-9]+/thing/regex",
2411 },
2412 skipAuthRoutes: []string{
2413 "GET=^/foo/bar",
2414 "POST=^/baz/[0-9]+/thing",
2415 "^/all/methods$",
2416 },
2417 expectedRoutes: []expectedAllowedRoute{
2419 method: "",
2420 regexString: "^/foo/bar/regex",
2421 },
2423 method: "",
2424 regexString: "^/baz/[0-9]+/thing/regex",
2425 },
2427 method: "GET",
2428 regexString: "^/foo/bar",
2429 },
2431 method: "POST",
2432 regexString: "^/baz/[0-9]+/thing",
2433 },
2435 method: "",
2436 regexString: "^/all/methods$",
2437 },
2438 },
2439 shouldError: false,
2440 },
2442 name: "Invalid skipAuthRegex entry",
2443 skipAuthRegex: []string{
2444 "^/foo/bar",
2445 "^/baz/[0-9]+/thing",
2446 "(bad[regex",
2447 },
2448 skipAuthRoutes: []string{},
2449 expectedRoutes: []expectedAllowedRoute{},
2450 shouldError: true,
2451 },
2453 name: "Invalid skipAuthRoutes entry",
2454 skipAuthRegex: []string{},
2455 skipAuthRoutes: []string{
2456 "GET=^/foo/bar",
2457 "POST=^/baz/[0-9]+/thing",
2458 "^/all/methods$",
2459 "PUT=(bad[regex",
2460 },
2461 expectedRoutes: []expectedAllowedRoute{},
2462 shouldError: true,
2463 },
2466 for _, tc := range testCases {
2467 t.Run(tc.name, func(t *testing.T) {
2468 opts := &options.Options{
2469 SkipAuthRegex: tc.skipAuthRegex,
2470 SkipAuthRoutes: tc.skipAuthRoutes,
2472 routes, err := buildRoutesAllowlist(opts)
2473 if tc.shouldError {
2474 assert.Error(t, err)
2475 return
2477 assert.NoError(t, err)
2479 for i, route := range routes {
2480 assert.Greater(t, len(tc.expectedRoutes), i)
2481 assert.Equal(t, route.method, tc.expectedRoutes[i].method)
2482 assert.Equal(t, route.pathRegex.String(), tc.expectedRoutes[i].regexString)
2484 })
2488func TestAllowedRequest(t *testing.T) {
2489 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2490 w.WriteHeader(200)
2491 _, err := w.Write([]byte("Allowed Request"))
2492 if err != nil {
2493 t.Fatal(err)
2495 }))
2496 t.Cleanup(upstreamServer.Close)
2498 opts := baseTestOptions()
2499 opts.UpstreamServers = options.Upstreams{
2501 ID: upstreamServer.URL,
2502 Path: "/",
2503 URI: upstreamServer.URL,
2504 },
2506 opts.SkipAuthRegex = []string{
2507 "^/skip/auth/regex$",
2509 opts.SkipAuthRoutes = []string{
2510 "GET=^/skip/auth/routes/get",
2512 err := validation.Validate(opts)
2513 assert.NoError(t, err)
2514 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2515 if err != nil {
2516 t.Fatal(err)
2519 testCases := []struct {
2520 name string
2521 method string
2522 url string
2523 allowed bool
2524 }{
2526 name: "Regex GET allowed",
2527 method: "GET",
2528 url: "/skip/auth/regex",
2529 allowed: true,
2530 },
2532 name: "Regex POST allowed ",
2533 method: "POST",
2534 url: "/skip/auth/regex",
2535 allowed: true,
2536 },
2538 name: "Regex denied",
2539 method: "GET",
2540 url: "/wrong/denied",
2541 allowed: false,
2542 },
2544 name: "Route allowed",
2545 method: "GET",
2546 url: "/skip/auth/routes/get",
2547 allowed: true,
2548 },
2550 name: "Route denied with wrong method",
2551 method: "PATCH",
2552 url: "/skip/auth/routes/get",
2553 allowed: false,
2554 },
2556 name: "Route denied with wrong path",
2557 method: "GET",
2558 url: "/skip/auth/routes/wrong/path",
2559 allowed: false,
2560 },
2563 for _, tc := range testCases {
2564 t.Run(tc.name, func(t *testing.T) {
2565 req, err := http.NewRequest(tc.method, tc.url, nil)
2566 assert.NoError(t, err)
2567 assert.Equal(t, tc.allowed, proxy.isAllowedRoute(req))
2569 rw := httptest.NewRecorder()
2570 proxy.ServeHTTP(rw, req)
2572 if tc.allowed {
2573 assert.Equal(t, 200, rw.Code)
2574 assert.Equal(t, "Allowed Request", rw.Body.String())
2575 } else {
2576 assert.Equal(t, 403, rw.Code)
2578 })
2582func TestProxyAllowedGroups(t *testing.T) {
2583 tests := []struct {
2584 name string
2585 allowedGroups []string
2586 groups []string
2587 expectUnauthorized bool
2588 }{
2589 {"NoAllowedGroups", []string{}, []string{}, false},
2590 {"NoAllowedGroupsUserHasGroups", []string{}, []string{"a", "b"}, false},
2591 {"UserInAllowedGroup", []string{"a"}, []string{"a", "b"}, false},
2592 {"UserNotInAllowedGroup", []string{"a"}, []string{"c"}, true},
2595 for _, tt := range tests {
2596 t.Run(tt.name, func(t *testing.T) {
2597 emailAddress := "test"
2598 created := time.Now()
2600 session := &sessions.SessionState{
2601 Groups: tt.groups,
2602 Email: emailAddress,
2603 AccessToken: "oauth_token",
2604 CreatedAt: &created,
2607 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2608 w.WriteHeader(200)
2609 }))
2610 t.Cleanup(upstreamServer.Close)
2612 test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
2613 opts.AllowedGroups = tt.allowedGroups
2614 opts.UpstreamServers = options.Upstreams{
2616 ID: upstreamServer.URL,
2617 Path: "/",
2618 URI: upstreamServer.URL,
2619 },
2621 })
2622 if err != nil {
2623 t.Fatal(err)
2626 test.req, _ = http.NewRequest("GET", "/", nil)
2628 test.req.Header.Add("accept", applicationJSON)
2629 err = test.SaveSession(session)
2630 assert.NoError(t, err)
2631 test.proxy.ServeHTTP(test.rw, test.req)
2633 if tt.expectUnauthorized {
2634 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
2635 } else {
2636 assert.Equal(t, http.StatusOK, test.rw.Code)
2638 })
2642func TestAuthOnlyAllowedGroups(t *testing.T) {
2643 tests := []struct {
2644 name string
2645 allowedGroups []string
2646 groups []string
2647 expectUnauthorized bool
2648 }{
2649 {"NoAllowedGroups", []string{}, []string{}, false},
2650 {"NoAllowedGroupsUserHasGroups", []string{}, []string{"a", "b"}, false},
2651 {"UserInAllowedGroup", []string{"a"}, []string{"a", "b"}, false},
2652 {"UserNotInAllowedGroup", []string{"a"}, []string{"c"}, true},
2655 for _, tt := range tests {
2656 t.Run(tt.name, func(t *testing.T) {
2657 emailAddress := "test"
2658 created := time.Now()
2660 session := &sessions.SessionState{
2661 Groups: tt.groups,
2662 Email: emailAddress,
2663 AccessToken: "oauth_token",
2664 CreatedAt: &created,
2667 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
2668 opts.AllowedGroups = tt.allowedGroups
2669 })
2670 if err != nil {
2671 t.Fatal(err)
2674 err = test.SaveSession(session)
2675 assert.NoError(t, err)
2677 test.proxy.ServeHTTP(test.rw, test.req)
2679 if tt.expectUnauthorized {
2680 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
2681 } else {
2682 assert.Equal(t, http.StatusAccepted, test.rw.Code)
2684 })