git.iko.soy/typeable/oauth2-proxy
on f8ddc13 help sign in
typeable/oauth2-proxy/oauthproxy_test.go
2858 lines 75 kB history raw
1package main
3import (
4 "context"
5 "crypto"
6 "encoding/base64"
7 "fmt"
8 "io"
9 "io/ioutil"
10 "net/http"
11 "net/http/httptest"
12 "net/url"
13 "regexp"
14 "strings"
15 "testing"
16 "time"
18 "github.com/coreos/go-oidc/v3/oidc"
19 "github.com/mbland/hmacauth"
20 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
21 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
22 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
23 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
24 internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
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: /\n", rw.Body.String())
62type TestProvider struct {
63 *providers.ProviderData
64 EmailAddress string
65 ValidToken bool
66 GroupValidator func(string) bool
69var _ providers.Provider = (*TestProvider)(nil)
71func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
72 return &TestProvider{
73 ProviderData: &providers.ProviderData{
74 ProviderName: "Test Provider",
75 LoginURL: &url.URL{
76 Scheme: "http",
77 Host: providerURL.Host,
78 Path: "/oauth/authorize",
79 },
80 RedeemURL: &url.URL{
81 Scheme: "http",
82 Host: providerURL.Host,
83 Path: "/oauth/token",
84 },
85 ProfileURL: &url.URL{
86 Scheme: "http",
87 Host: providerURL.Host,
88 Path: "/api/v1/profile",
89 },
90 Scope: "profile.email",
91 },
92 EmailAddress: emailAddress,
93 GroupValidator: func(s string) bool {
94 return true
95 },
96 }
99func (tp *TestProvider) GetEmailAddress(_ context.Context, _ *sessions.SessionState) (string, error) {
100 return tp.EmailAddress, nil
103func (tp *TestProvider) ValidateSession(_ context.Context, _ *sessions.SessionState) bool {
104 return tp.ValidToken
107func Test_redeemCode(t *testing.T) {
108 opts := baseTestOptions()
109 err := validation.Validate(opts)
110 assert.NoError(t, err)
112 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
113 if err != nil {
114 t.Fatal(err)
115 }
117 req := httptest.NewRequest(http.MethodGet, "/", nil)
118 _, err = proxy.redeemCode(req, "")
119 assert.Equal(t, providers.ErrMissingCode, err)
122func Test_enrichSession(t *testing.T) {
123 const (
124 sessionUser = "Mr Session"
125 sessionEmail = "session@example.com"
126 providerEmail = "provider@example.com"
127 )
129 testCases := map[string]struct {
130 session *sessions.SessionState
131 expectedUser string
132 expectedEmail string
133 }{
134 "Session already has enrichable fields": {
135 session: &sessions.SessionState{
136 User: sessionUser,
137 Email: sessionEmail,
138 },
139 expectedUser: sessionUser,
140 expectedEmail: sessionEmail,
141 },
142 "Session is missing Email and GetEmailAddress is implemented": {
143 session: &sessions.SessionState{
144 User: sessionUser,
145 },
146 expectedUser: sessionUser,
147 expectedEmail: providerEmail,
148 },
149 "Session is missing User and GetUserName is not implemented": {
150 session: &sessions.SessionState{
151 Email: sessionEmail,
152 },
153 expectedUser: "",
154 expectedEmail: sessionEmail,
155 },
156 }
158 for name, tc := range testCases {
159 t.Run(name, func(t *testing.T) {
160 opts := baseTestOptions()
161 err := validation.Validate(opts)
162 assert.NoError(t, err)
164 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
165 if err != nil {
166 t.Fatal(err)
167 }
168 proxy.provider = NewTestProvider(&url.URL{Host: "www.example.com"}, providerEmail)
170 err = proxy.enrichSessionState(context.Background(), tc.session)
171 assert.NoError(t, err)
172 assert.Equal(t, tc.expectedUser, tc.session.User)
173 assert.Equal(t, tc.expectedEmail, tc.session.Email)
174 })
175 }
178func TestBasicAuthPassword(t *testing.T) {
179 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
180 logger.Printf("%#v", r)
181 var payload string
182 switch r.URL.Path {
183 case "/oauth/token":
184 payload = `{"access_token": "my_auth_token"}`
185 default:
186 payload = r.Header.Get("Authorization")
187 if payload == "" {
188 payload = "No Authorization header found."
189 }
190 }
191 w.WriteHeader(200)
192 _, err := w.Write([]byte(payload))
193 if err != nil {
194 t.Fatal(err)
195 }
196 }))
198 basicAuthPassword := "This is a secure password"
199 opts := baseTestOptions()
200 opts.UpstreamServers = options.UpstreamConfig{
201 Upstreams: []options.Upstream{
202 {
203 ID: providerServer.URL,
204 Path: "/",
205 URI: providerServer.URL,
206 },
207 },
208 }
210 opts.Cookie.Secure = false
211 opts.InjectRequestHeaders = []options.Header{
212 {
213 Name: "Authorization",
214 Values: []options.HeaderValue{
215 {
216 ClaimSource: &options.ClaimSource{
217 Claim: "email",
218 BasicAuthPassword: &options.SecretSource{
219 Value: []byte(basicAuthPassword),
220 },
221 },
222 },
223 },
224 },
225 }
227 err := validation.Validate(opts)
228 assert.NoError(t, err)
230 providerURL, _ := url.Parse(providerServer.URL)
231 const emailAddress = "john.doe@example.com"
233 proxy, err := NewOAuthProxy(opts, func(email string) bool {
234 return email == emailAddress
235 })
236 if err != nil {
237 t.Fatal(err)
238 }
239 proxy.provider = NewTestProvider(providerURL, emailAddress)
241 // Save the required session
242 rw := httptest.NewRecorder()
243 req, _ := http.NewRequest("GET", "/", nil)
244 err = proxy.sessionStore.Save(rw, req, &sessions.SessionState{
245 Email: emailAddress,
246 })
247 assert.NoError(t, err)
249 // Extract the cookie value to inject into the test request
250 cookie := rw.Header().Values("Set-Cookie")[0]
252 req, _ = http.NewRequest("GET", "/", nil)
253 req.Header.Set("Cookie", cookie)
254 rw = httptest.NewRecorder()
255 proxy.ServeHTTP(rw, req)
257 // The username in the basic auth credentials is expected to be equal to the email address from the
258 // auth response, so we use the same variable here.
259 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+basicAuthPassword))
260 assert.Equal(t, expectedHeader, rw.Body.String())
261 providerServer.Close()
264func TestPassGroupsHeadersWithGroups(t *testing.T) {
265 opts := baseTestOptions()
266 opts.InjectRequestHeaders = []options.Header{
267 {
268 Name: "X-Forwarded-Groups",
269 Values: []options.HeaderValue{
270 {
271 ClaimSource: &options.ClaimSource{
272 Claim: "groups",
273 },
274 },
275 },
276 },
277 }
279 err := validation.Validate(opts)
280 assert.NoError(t, err)
282 const emailAddress = "john.doe@example.com"
283 const userName = "9fcab5c9b889a557"
285 groups := []string{"a", "b"}
286 created := time.Now()
287 session := &sessions.SessionState{
288 User: userName,
289 Groups: groups,
290 Email: emailAddress,
291 AccessToken: "oauth_token",
292 CreatedAt: &created,
293 }
295 proxy, err := NewOAuthProxy(opts, func(email string) bool {
296 return email == emailAddress
297 })
298 assert.NoError(t, err)
300 // Save the required session
301 rw := httptest.NewRecorder()
302 req, _ := http.NewRequest("GET", "/", nil)
303 err = proxy.sessionStore.Save(rw, req, session)
304 assert.NoError(t, err)
306 // Extract the cookie value to inject into the test request
307 cookie := rw.Header().Values("Set-Cookie")[0]
309 req, _ = http.NewRequest("GET", "/", nil)
310 req.Header.Set("Cookie", cookie)
311 rw = httptest.NewRecorder()
312 proxy.ServeHTTP(rw, req)
314 assert.Equal(t, []string{"a,b"}, req.Header["X-Forwarded-Groups"])
317type PassAccessTokenTest struct {
318 providerServer *httptest.Server
319 proxy *OAuthProxy
320 opts *options.Options
323type PassAccessTokenTestOptions struct {
324 PassAccessToken bool
325 ValidToken bool
326 ProxyUpstream options.Upstream
329func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTest, error) {
330 patt := &PassAccessTokenTest{}
332 patt.providerServer = httptest.NewServer(
333 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
334 var payload string
335 switch r.URL.Path {
336 case "/oauth/token":
337 payload = `{"access_token": "my_auth_token"}`
338 default:
339 payload = r.Header.Get("X-Forwarded-Access-Token")
340 if payload == "" {
341 payload = "No access token found."
342 }
343 }
344 w.WriteHeader(200)
345 _, err := w.Write([]byte(payload))
346 if err != nil {
347 panic(err)
348 }
349 }))
351 patt.opts = baseTestOptions()
352 patt.opts.UpstreamServers = options.UpstreamConfig{
353 Upstreams: []options.Upstream{
354 {
355 ID: patt.providerServer.URL,
356 Path: "/",
357 URI: patt.providerServer.URL,
358 },
359 },
360 }
361 if opts.ProxyUpstream.ID != "" {
362 patt.opts.UpstreamServers.Upstreams = append(patt.opts.UpstreamServers.Upstreams, opts.ProxyUpstream)
363 }
365 patt.opts.Cookie.Secure = false
366 if opts.PassAccessToken {
367 patt.opts.InjectRequestHeaders = []options.Header{
368 {
369 Name: "X-Forwarded-Access-Token",
370 Values: []options.HeaderValue{
371 {
372 ClaimSource: &options.ClaimSource{
373 Claim: "access_token",
374 },
375 },
376 },
377 },
378 }
379 }
381 err := validation.Validate(patt.opts)
382 if err != nil {
383 return nil, err
384 }
386 providerURL, _ := url.Parse(patt.providerServer.URL)
387 const emailAddress = "michael.bland@gsa.gov"
389 testProvider := NewTestProvider(providerURL, emailAddress)
390 testProvider.ValidToken = opts.ValidToken
391 patt.proxy, err = NewOAuthProxy(patt.opts, func(email string) bool {
392 return email == emailAddress
393 })
394 patt.proxy.provider = testProvider
395 if err != nil {
396 return nil, err
397 }
398 return patt, nil
401func (patTest *PassAccessTokenTest) Close() {
402 patTest.providerServer.Close()
405func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int, cookie string) {
406 rw := httptest.NewRecorder()
408 csrf, err := cookies.NewCSRF(patTest.proxy.CookieOptions, "")
409 if err != nil {
410 panic(err)
411 }
413 req, err := http.NewRequest(
414 http.MethodGet,
415 fmt.Sprintf(
416 "/oauth2/callback?code=callback_code&state=%s",
417 encodeState(csrf.HashOAuthState(), "%2F"),
418 ),
419 strings.NewReader(""),
420 )
421 if err != nil {
422 return 0, ""
423 }
425 // rw is a dummy here, we just want the csrfCookie to add to our req
426 csrfCookie, err := csrf.SetCookie(httptest.NewRecorder(), req)
427 if err != nil {
428 panic(err)
429 }
430 req.AddCookie(csrfCookie)
432 patTest.proxy.ServeHTTP(rw, req)
434 if len(rw.Header().Values("Set-Cookie")) >= 2 {
435 cookie = rw.Header().Values("Set-Cookie")[1]
436 }
438 return rw.Code, cookie
441// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
442// and cookie and returns body and status code.
443func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
444 cookieName := patTest.proxy.CookieOptions.Name
445 var value string
446 keyPrefix := cookieName + "="
448 for _, field := range strings.Split(cookie, "; ") {
449 value = strings.TrimPrefix(field, keyPrefix)
450 if value != field {
451 break
452 } else {
453 value = ""
454 }
455 }
456 if value == "" {
457 return 0, ""
458 }
460 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
461 if err != nil {
462 return 0, ""
463 }
464 req.AddCookie(&http.Cookie{
465 Name: cookieName,
466 Value: value,
467 Path: "/",
468 Expires: time.Now().Add(time.Duration(24)),
469 HttpOnly: true,
470 })
472 rw := httptest.NewRecorder()
473 patTest.proxy.ServeHTTP(rw, req)
474 return rw.Code, rw.Body.String()
477func TestForwardAccessTokenUpstream(t *testing.T) {
478 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
479 PassAccessToken: true,
480 ValidToken: true,
481 })
482 if err != nil {
483 t.Fatal(err)
484 }
485 t.Cleanup(patTest.Close)
487 // A successful validation will redirect and set the auth cookie.
488 code, cookie := patTest.getCallbackEndpoint()
489 if code != 302 {
490 t.Fatalf("expected 302; got %d", code)
491 }
492 assert.NotNil(t, cookie)
494 // Now we make a regular request; the access_token from the cookie is
495 // forwarded as the "X-Forwarded-Access-Token" header. The token is
496 // read by the test provider server and written in the response body.
497 code, payload := patTest.getEndpointWithCookie(cookie, "/")
498 if code != 200 {
499 t.Fatalf("expected 200; got %d", code)
500 }
501 assert.Equal(t, "my_auth_token", payload)
504func TestStaticProxyUpstream(t *testing.T) {
505 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
506 PassAccessToken: true,
507 ValidToken: true,
508 ProxyUpstream: options.Upstream{
509 ID: "static-proxy",
510 Path: "/static-proxy",
511 Static: true,
512 },
513 })
514 if err != nil {
515 t.Fatal(err)
516 }
517 t.Cleanup(patTest.Close)
519 // A successful validation will redirect and set the auth cookie.
520 code, cookie := patTest.getCallbackEndpoint()
521 if code != 302 {
522 t.Fatalf("expected 302; got %d", code)
523 }
524 assert.NotEqual(t, nil, cookie)
526 // Now we make a regular request against the upstream proxy; And validate
527 // the returned status code through the static proxy.
528 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
529 if code != 200 {
530 t.Fatalf("expected 200; got %d", code)
531 }
532 assert.Equal(t, "Authenticated", payload)
535func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
536 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
537 PassAccessToken: false,
538 ValidToken: true,
539 })
540 if err != nil {
541 t.Fatal(err)
542 }
543 t.Cleanup(patTest.Close)
545 // A successful validation will redirect and set the auth cookie.
546 code, cookie := patTest.getCallbackEndpoint()
547 if code != 302 {
548 t.Fatalf("expected 302; got %d", code)
549 }
550 assert.NotEqual(t, nil, cookie)
552 // Now we make a regular request, but the access token header should
553 // not be present.
554 code, payload := patTest.getEndpointWithCookie(cookie, "/")
555 if code != 200 {
556 t.Fatalf("expected 200; got %d", code)
557 }
558 assert.Equal(t, "No access token found.", payload)
561func TestSessionValidationFailure(t *testing.T) {
562 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
563 ValidToken: false,
564 })
565 require.NoError(t, err)
566 t.Cleanup(patTest.Close)
568 // An unsuccessful validation will return 403 and not set the auth cookie.
569 code, cookie := patTest.getCallbackEndpoint()
570 assert.Equal(t, http.StatusForbidden, code)
571 assert.Equal(t, "", cookie)
574type SignInPageTest struct {
575 opts *options.Options
576 proxy *OAuthProxy
577 signInRegexp *regexp.Regexp
578 signInProviderRegexp *regexp.Regexp
581const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
582const signInSkipProvider = `>Found<`
584func NewSignInPageTest(skipProvider bool) (*SignInPageTest, error) {
585 var sipTest SignInPageTest
587 sipTest.opts = baseTestOptions()
588 sipTest.opts.SkipProviderButton = skipProvider
589 err := validation.Validate(sipTest.opts)
590 if err != nil {
591 return nil, err
592 }
594 sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
595 return true
596 })
597 if err != nil {
598 return nil, err
599 }
600 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
601 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
603 return &sipTest, nil
606func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
607 rw := httptest.NewRecorder()
608 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
609 sipTest.proxy.ServeHTTP(rw, req)
610 return rw.Code, rw.Body.String()
613type AlwaysSuccessfulValidator struct {
616func (AlwaysSuccessfulValidator) Validate(user, password string) bool {
617 return true
620func TestManualSignInStoresUserGroupsInTheSession(t *testing.T) {
621 userGroups := []string{"somegroup", "someothergroup"}
623 opts := baseTestOptions()
624 opts.HtpasswdUserGroups = userGroups
625 err := validation.Validate(opts)
626 if err != nil {
627 t.Fatal(err)
628 }
630 proxy, err := NewOAuthProxy(opts, func(email string) bool {
631 return true
632 })
633 if err != nil {
634 t.Fatal(err)
635 }
636 proxy.basicAuthValidator = AlwaysSuccessfulValidator{}
638 rw := httptest.NewRecorder()
639 formData := url.Values{}
640 formData.Set("username", "someuser")
641 formData.Set("password", "somepass")
642 signInReq, _ := http.NewRequest(http.MethodPost, "/oauth2/sign_in", strings.NewReader(formData.Encode()))
643 signInReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
644 proxy.ServeHTTP(rw, signInReq)
646 assert.Equal(t, http.StatusFound, rw.Code)
648 req, _ := http.NewRequest(http.MethodGet, "/something", strings.NewReader(formData.Encode()))
649 for _, c := range rw.Result().Cookies() {
650 req.AddCookie(c)
651 }
653 s, err := proxy.sessionStore.Load(req)
654 if err != nil {
655 t.Fatal(err)
656 }
657 assert.Equal(t, userGroups, s.Groups)
660func TestSignInPageIncludesTargetRedirect(t *testing.T) {
661 sipTest, err := NewSignInPageTest(false)
662 if err != nil {
663 t.Fatal(err)
664 }
665 const endpoint = "/some/random/endpoint"
667 code, body := sipTest.GetEndpoint(endpoint)
668 assert.Equal(t, 403, code)
670 match := sipTest.signInRegexp.FindStringSubmatch(body)
671 if match == nil {
672 t.Fatal("Did not find pattern in body: " +
673 signInRedirectPattern + "\nBody:\n" + body)
674 }
675 if match[1] != endpoint {
676 t.Fatal(`expected redirect to "` + endpoint +
677 `", but was "` + match[1] + `"`)
678 }
681func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
682 sipTest, err := NewSignInPageTest(false)
683 if err != nil {
684 t.Fatal(err)
685 }
686 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
687 assert.Equal(t, 200, code)
689 match := sipTest.signInRegexp.FindStringSubmatch(body)
690 if match == nil {
691 t.Fatal("Did not find pattern in body: " +
692 signInRedirectPattern + "\nBody:\n" + body)
693 }
694 if match[1] != "/" {
695 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
696 }
699func TestSignInPageSkipProvider(t *testing.T) {
700 sipTest, err := NewSignInPageTest(true)
701 if err != nil {
702 t.Fatal(err)
703 }
705 endpoint := "/some/random/endpoint"
707 code, body := sipTest.GetEndpoint(endpoint)
708 assert.Equal(t, 302, code)
710 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
711 if match == nil {
712 t.Fatal("Did not find pattern in body: " +
713 signInSkipProvider + "\nBody:\n" + body)
714 }
717func TestSignInPageSkipProviderDirect(t *testing.T) {
718 sipTest, err := NewSignInPageTest(true)
719 if err != nil {
720 t.Fatal(err)
721 }
723 endpoint := "/sign_in"
725 code, body := sipTest.GetEndpoint(endpoint)
726 assert.Equal(t, 302, code)
728 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
729 if match == nil {
730 t.Fatal("Did not find pattern in body: " +
731 signInSkipProvider + "\nBody:\n" + body)
732 }
735type ProcessCookieTest struct {
736 opts *options.Options
737 proxy *OAuthProxy
738 rw *httptest.ResponseRecorder
739 req *http.Request
740 validateUser bool
743type ProcessCookieTestOpts struct {
744 providerValidateCookieResponse bool
747type OptionsModifier func(*options.Options)
749func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
750 var pcTest ProcessCookieTest
752 pcTest.opts = baseTestOptions()
753 for _, modifier := range modifiers {
754 modifier(pcTest.opts)
755 }
756 // First, set the CookieRefresh option so proxy.AesCipher is created,
757 // needed to encrypt the access_token.
758 pcTest.opts.Cookie.Refresh = time.Hour
759 err := validation.Validate(pcTest.opts)
760 if err != nil {
761 return nil, err
762 }
764 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
765 return pcTest.validateUser
766 })
767 if err != nil {
768 return nil, err
769 }
770 testProvider := &TestProvider{
771 ProviderData: &providers.ProviderData{},
772 ValidToken: opts.providerValidateCookieResponse,
773 }
775 groups := pcTest.opts.Providers[0].AllowedGroups
776 testProvider.AllowedGroups = make(map[string]struct{}, len(groups))
777 for _, group := range groups {
778 testProvider.AllowedGroups[group] = struct{}{}
779 }
780 pcTest.proxy.provider = testProvider
782 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
783 // access_token validation.
784 pcTest.proxy.CookieOptions.Refresh = time.Duration(0)
785 pcTest.rw = httptest.NewRecorder()
786 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
787 pcTest.validateUser = true
788 return &pcTest, nil
791func NewProcessCookieTestWithDefaults() (*ProcessCookieTest, error) {
792 return NewProcessCookieTest(ProcessCookieTestOpts{
793 providerValidateCookieResponse: true,
794 })
797func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
798 return NewProcessCookieTest(ProcessCookieTestOpts{
799 providerValidateCookieResponse: true,
800 }, modifiers...)
803func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
804 err := p.proxy.SaveSession(p.rw, p.req, s)
805 if err != nil {
806 return err
807 }
808 for _, cookie := range p.rw.Result().Cookies() {
809 p.req.AddCookie(cookie)
810 }
811 return nil
814func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
815 return p.proxy.LoadCookiedSession(p.req)
818func TestLoadCookiedSession(t *testing.T) {
819 pcTest, err := NewProcessCookieTestWithDefaults()
820 if err != nil {
821 t.Fatal(err)
822 }
824 created := time.Now()
825 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
826 err = pcTest.SaveSession(startSession)
827 assert.NoError(t, err)
829 session, err := pcTest.LoadCookiedSession()
830 if err != nil {
831 t.Fatal(err)
832 }
833 assert.Equal(t, startSession.Email, session.Email)
834 assert.Equal(t, "", session.User)
835 assert.Equal(t, startSession.AccessToken, session.AccessToken)
838func TestProcessCookieNoCookieError(t *testing.T) {
839 pcTest, err := NewProcessCookieTestWithDefaults()
840 if err != nil {
841 t.Fatal(err)
842 }
844 session, err := pcTest.LoadCookiedSession()
845 assert.Error(t, err, "cookie \"_oauth2_proxy\" not present")
846 if session != nil {
847 t.Errorf("expected nil session. got %#v", session)
848 }
851func TestProcessCookieRefreshNotSet(t *testing.T) {
852 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
853 opts.Cookie.Expire = time.Duration(23) * time.Hour
854 })
855 if err != nil {
856 t.Fatal(err)
857 }
859 reference := time.Now().Add(time.Duration(-2) * time.Hour)
861 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
862 err = pcTest.SaveSession(startSession)
863 assert.NoError(t, err)
865 session, err := pcTest.LoadCookiedSession()
866 assert.Equal(t, nil, err)
867 if session.Age() < time.Duration(-2)*time.Hour {
868 t.Errorf("cookie too young %v", session.Age())
869 }
870 assert.Equal(t, startSession.Email, session.Email)
873func TestProcessCookieFailIfCookieExpired(t *testing.T) {
874 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
875 opts.Cookie.Expire = time.Duration(24) * time.Hour
876 })
877 if err != nil {
878 t.Fatal(err)
879 }
881 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
882 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
883 err = pcTest.SaveSession(startSession)
884 assert.NoError(t, err)
886 session, err := pcTest.LoadCookiedSession()
887 assert.NotEqual(t, nil, err)
888 if session != nil {
889 t.Errorf("expected nil session %#v", session)
890 }
893func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
894 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
895 opts.Cookie.Expire = time.Duration(24) * time.Hour
896 })
897 if err != nil {
898 t.Fatal(err)
899 }
901 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
902 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
903 err = pcTest.SaveSession(startSession)
904 assert.NoError(t, err)
906 pcTest.proxy.CookieOptions.Refresh = time.Hour
907 session, err := pcTest.LoadCookiedSession()
908 assert.NotEqual(t, nil, err)
909 if session != nil {
910 t.Errorf("expected nil session %#v", session)
911 }
914func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
915 pcTest, err := NewProcessCookieTestWithDefaults()
916 if err != nil {
917 return nil, err
918 }
919 pcTest.req, _ = http.NewRequest("GET",
920 pcTest.opts.ProxyPrefix+"/userinfo", nil)
921 return pcTest, nil
924func TestUserInfoEndpointAccepted(t *testing.T) {
925 testCases := []struct {
926 name string
927 session *sessions.SessionState
928 expectedResponse string
929 }{
930 {
931 name: "Full session",
932 session: &sessions.SessionState{
933 User: "john.doe",
934 Email: "john.doe@example.com",
935 Groups: []string{"example", "groups"},
936 AccessToken: "my_access_token",
937 },
938 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n",
939 },
940 {
941 name: "Minimal session",
942 session: &sessions.SessionState{
943 User: "john.doe",
944 Email: "john.doe@example.com",
945 Groups: []string{"example", "groups"},
946 },
947 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n",
948 },
949 {
950 name: "No groups",
951 session: &sessions.SessionState{
952 User: "john.doe",
953 Email: "john.doe@example.com",
954 AccessToken: "my_access_token",
955 },
956 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\"}\n",
957 },
958 {
959 name: "With Preferred Username",
960 session: &sessions.SessionState{
961 User: "john.doe",
962 PreferredUsername: "john",
963 Email: "john.doe@example.com",
964 Groups: []string{"example", "groups"},
965 AccessToken: "my_access_token",
966 },
967 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"],\"preferredUsername\":\"john\"}\n",
968 },
969 }
971 for _, tc := range testCases {
972 t.Run(tc.name, func(t *testing.T) {
973 test, err := NewUserInfoEndpointTest()
974 if err != nil {
975 t.Fatal(err)
976 }
977 err = test.SaveSession(tc.session)
978 assert.NoError(t, err)
980 test.proxy.ServeHTTP(test.rw, test.req)
981 assert.Equal(t, http.StatusOK, test.rw.Code)
982 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
983 assert.Equal(t, tc.expectedResponse, string(bodyBytes))
984 })
985 }
988func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
989 test, err := NewUserInfoEndpointTest()
990 if err != nil {
991 t.Fatal(err)
992 }
994 test.proxy.ServeHTTP(test.rw, test.req)
995 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
998func TestEncodedUrlsStayEncoded(t *testing.T) {
999 encodeTest, err := NewSignInPageTest(false)
1000 if err != nil {
1001 t.Fatal(err)
1003 code, _ := encodeTest.GetEndpoint("/%2F/test1/%2F/test2")
1004 assert.Equal(t, 403, code)
1007func NewAuthOnlyEndpointTest(querystring string, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1008 pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1009 if err != nil {
1010 return nil, err
1012 pcTest.req, _ = http.NewRequest(
1013 "GET",
1014 fmt.Sprintf("%s/auth%s", pcTest.opts.ProxyPrefix, querystring),
1015 nil)
1016 return pcTest, nil
1019func TestAuthOnlyEndpointAccepted(t *testing.T) {
1020 test, err := NewAuthOnlyEndpointTest("")
1021 if err != nil {
1022 t.Fatal(err)
1025 created := time.Now()
1026 startSession := &sessions.SessionState{
1027 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1028 err = test.SaveSession(startSession)
1029 assert.NoError(t, err)
1031 test.proxy.ServeHTTP(test.rw, test.req)
1032 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1033 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1034 assert.Equal(t, "", string(bodyBytes))
1037func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1038 test, err := NewAuthOnlyEndpointTest("")
1039 if err != nil {
1040 t.Fatal(err)
1043 test.proxy.ServeHTTP(test.rw, test.req)
1044 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1045 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1046 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1049func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1050 test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
1051 opts.Cookie.Expire = time.Duration(24) * time.Hour
1052 })
1053 if err != nil {
1054 t.Fatal(err)
1057 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1058 startSession := &sessions.SessionState{
1059 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1060 err = test.SaveSession(startSession)
1061 assert.NoError(t, err)
1063 test.proxy.ServeHTTP(test.rw, test.req)
1064 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1065 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1066 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1069func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1070 test, err := NewAuthOnlyEndpointTest("")
1071 if err != nil {
1072 t.Fatal(err)
1075 created := time.Now()
1076 startSession := &sessions.SessionState{
1077 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1078 err = test.SaveSession(startSession)
1079 assert.NoError(t, err)
1080 test.validateUser = false
1082 test.proxy.ServeHTTP(test.rw, test.req)
1083 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1084 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1085 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1088func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1089 var pcTest ProcessCookieTest
1091 pcTest.opts = baseTestOptions()
1092 pcTest.opts.InjectResponseHeaders = []options.Header{
1094 Name: "X-Auth-Request-User",
1095 Values: []options.HeaderValue{
1097 ClaimSource: &options.ClaimSource{
1098 Claim: "user",
1099 },
1100 },
1101 },
1102 },
1104 Name: "X-Auth-Request-Email",
1105 Values: []options.HeaderValue{
1107 ClaimSource: &options.ClaimSource{
1108 Claim: "email",
1109 },
1110 },
1111 },
1112 },
1114 Name: "X-Auth-Request-Groups",
1115 Values: []options.HeaderValue{
1117 ClaimSource: &options.ClaimSource{
1118 Claim: "groups",
1119 },
1120 },
1121 },
1122 },
1124 Name: "X-Forwarded-Preferred-Username",
1125 Values: []options.HeaderValue{
1127 ClaimSource: &options.ClaimSource{
1128 Claim: "preferred_username",
1129 },
1130 },
1131 },
1132 },
1134 pcTest.opts.Providers[0].AllowedGroups = []string{"oauth_groups"}
1135 err := validation.Validate(pcTest.opts)
1136 assert.NoError(t, err)
1138 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1139 return pcTest.validateUser
1140 })
1141 if err != nil {
1142 t.Fatal(err)
1144 pcTest.proxy.provider = &TestProvider{
1145 ProviderData: &providers.ProviderData{},
1146 ValidToken: true,
1149 pcTest.validateUser = true
1151 pcTest.rw = httptest.NewRecorder()
1152 pcTest.req, _ = http.NewRequest("GET",
1153 pcTest.opts.ProxyPrefix+"/auth", nil)
1155 created := time.Now()
1156 startSession := &sessions.SessionState{
1157 User: "oauth_user", Groups: []string{"oauth_groups"}, Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1158 err = pcTest.SaveSession(startSession)
1159 assert.NoError(t, err)
1161 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1162 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1163 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1164 assert.Equal(t, startSession.Groups, pcTest.rw.Header().Values("X-Auth-Request-Groups"))
1165 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1168func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1169 var pcTest ProcessCookieTest
1171 pcTest.opts = baseTestOptions()
1172 pcTest.opts.InjectResponseHeaders = []options.Header{
1174 Name: "X-Auth-Request-User",
1175 Values: []options.HeaderValue{
1177 ClaimSource: &options.ClaimSource{
1178 Claim: "user",
1179 },
1180 },
1181 },
1182 },
1184 Name: "X-Auth-Request-Email",
1185 Values: []options.HeaderValue{
1187 ClaimSource: &options.ClaimSource{
1188 Claim: "email",
1189 },
1190 },
1191 },
1192 },
1194 Name: "X-Auth-Request-Groups",
1195 Values: []options.HeaderValue{
1197 ClaimSource: &options.ClaimSource{
1198 Claim: "groups",
1199 },
1200 },
1201 },
1202 },
1204 Name: "X-Forwarded-Preferred-Username",
1205 Values: []options.HeaderValue{
1207 ClaimSource: &options.ClaimSource{
1208 Claim: "preferred_username",
1209 },
1210 },
1211 },
1212 },
1214 Name: "Authorization",
1215 Values: []options.HeaderValue{
1217 ClaimSource: &options.ClaimSource{
1218 Claim: "user",
1219 BasicAuthPassword: &options.SecretSource{
1220 Value: []byte("This is a secure password"),
1221 },
1222 },
1223 },
1224 },
1225 },
1228 err := validation.Validate(pcTest.opts)
1229 assert.NoError(t, err)
1231 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1232 return pcTest.validateUser
1233 })
1234 if err != nil {
1235 t.Fatal(err)
1237 pcTest.proxy.provider = &TestProvider{
1238 ProviderData: &providers.ProviderData{},
1239 ValidToken: true,
1242 pcTest.validateUser = true
1244 pcTest.rw = httptest.NewRecorder()
1245 pcTest.req, _ = http.NewRequest("GET",
1246 pcTest.opts.ProxyPrefix+"/auth", nil)
1248 created := time.Now()
1249 startSession := &sessions.SessionState{
1250 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1251 err = pcTest.SaveSession(startSession)
1252 assert.NoError(t, err)
1254 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1255 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1256 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1257 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1258 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:This is a secure password"))
1259 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1262func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1263 var pcTest ProcessCookieTest
1265 pcTest.opts = baseTestOptions()
1266 pcTest.opts.InjectResponseHeaders = []options.Header{
1268 Name: "X-Auth-Request-User",
1269 Values: []options.HeaderValue{
1271 ClaimSource: &options.ClaimSource{
1272 Claim: "user",
1273 },
1274 },
1275 },
1276 },
1278 Name: "X-Auth-Request-Email",
1279 Values: []options.HeaderValue{
1281 ClaimSource: &options.ClaimSource{
1282 Claim: "email",
1283 },
1284 },
1285 },
1286 },
1288 Name: "X-Auth-Request-Groups",
1289 Values: []options.HeaderValue{
1291 ClaimSource: &options.ClaimSource{
1292 Claim: "groups",
1293 },
1294 },
1295 },
1296 },
1298 Name: "X-Forwarded-Preferred-Username",
1299 Values: []options.HeaderValue{
1301 ClaimSource: &options.ClaimSource{
1302 Claim: "preferred_username",
1303 },
1304 },
1305 },
1306 },
1308 err := validation.Validate(pcTest.opts)
1309 assert.NoError(t, err)
1311 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1312 return pcTest.validateUser
1313 })
1314 if err != nil {
1315 t.Fatal(err)
1317 pcTest.proxy.provider = &TestProvider{
1318 ProviderData: &providers.ProviderData{},
1319 ValidToken: true,
1322 pcTest.validateUser = true
1324 pcTest.rw = httptest.NewRecorder()
1325 pcTest.req, _ = http.NewRequest("GET",
1326 pcTest.opts.ProxyPrefix+"/auth", nil)
1328 created := time.Now()
1329 startSession := &sessions.SessionState{
1330 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1331 err = pcTest.SaveSession(startSession)
1332 assert.NoError(t, err)
1334 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1335 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1336 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1337 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1338 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1341func TestAuthSkippedForPreflightRequests(t *testing.T) {
1342 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1343 w.WriteHeader(200)
1344 _, err := w.Write([]byte("response"))
1345 if err != nil {
1346 t.Fatal(err)
1348 }))
1349 t.Cleanup(upstreamServer.Close)
1351 opts := baseTestOptions()
1352 opts.UpstreamServers = options.UpstreamConfig{
1353 Upstreams: []options.Upstream{
1355 ID: upstreamServer.URL,
1356 Path: "/",
1357 URI: upstreamServer.URL,
1358 },
1359 },
1361 opts.SkipAuthPreflight = true
1362 err := validation.Validate(opts)
1363 assert.NoError(t, err)
1365 upstreamURL, _ := url.Parse(upstreamServer.URL)
1367 proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
1368 if err != nil {
1369 t.Fatal(err)
1371 proxy.provider = NewTestProvider(upstreamURL, "")
1372 rw := httptest.NewRecorder()
1373 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1374 proxy.ServeHTTP(rw, req)
1376 assert.Equal(t, 200, rw.Code)
1377 assert.Equal(t, "response", rw.Body.String())
1380type SignatureAuthenticator struct {
1381 auth hmacauth.HmacAuth
1384func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1385 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1387 var msg string
1388 switch result {
1389 case hmacauth.ResultNoSignature:
1390 msg = "no signature received"
1391 case hmacauth.ResultMatch:
1392 msg = "signatures match"
1393 case hmacauth.ResultMismatch:
1394 msg = fmt.Sprintf(
1395 "signatures do not match:\n received: %s\n computed: %s",
1396 headerSig,
1397 computedSig)
1398 default:
1399 panic("unknown result value: " + result.String())
1402 _, err := w.Write([]byte(msg))
1403 if err != nil {
1404 panic(err)
1408type SignatureTest struct {
1409 opts *options.Options
1410 upstream *httptest.Server
1411 upstreamHost string
1412 provider *httptest.Server
1413 header http.Header
1414 rw *httptest.ResponseRecorder
1415 authenticator *SignatureAuthenticator
1416 authProvider providers.Provider
1419func NewSignatureTest() (*SignatureTest, error) {
1420 opts := baseTestOptions()
1421 opts.EmailDomains = []string{"acm.org"}
1423 authenticator := &SignatureAuthenticator{}
1424 upstreamServer := httptest.NewServer(
1425 http.HandlerFunc(authenticator.Authenticate))
1426 upstreamURL, err := url.Parse(upstreamServer.URL)
1427 if err != nil {
1428 return nil, err
1430 opts.UpstreamServers = options.UpstreamConfig{
1431 Upstreams: []options.Upstream{
1433 ID: upstreamServer.URL,
1434 Path: "/",
1435 URI: upstreamServer.URL,
1436 },
1437 },
1440 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1441 _, err := w.Write([]byte(`{"access_token": "my_auth_token"}`))
1442 if err != nil {
1443 panic(err)
1446 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1447 providerURL, err := url.Parse(provider.URL)
1448 if err != nil {
1449 return nil, err
1451 testProvider := NewTestProvider(providerURL, "mbland@acm.org")
1453 return &SignatureTest{
1454 opts,
1455 upstreamServer,
1456 upstreamURL.Host,
1457 provider,
1458 make(http.Header),
1459 httptest.NewRecorder(),
1460 authenticator,
1461 testProvider,
1462 }, nil
1465func (st *SignatureTest) Close() {
1466 st.provider.Close()
1467 st.upstream.Close()
1470// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1471// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1472// https://github.com/18F/hmacauth/pull/4
1473type fakeNetConn struct {
1474 reqBody string
1477func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1478 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1479 copy(p, fnc.reqBody)
1480 fnc.reqBody = ""
1481 return bodyLen, io.EOF
1483 return 0, io.EOF
1486func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) error {
1487 err := validation.Validate(st.opts)
1488 if err != nil {
1489 return err
1491 proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
1492 if err != nil {
1493 return err
1495 proxy.provider = st.authProvider
1497 var bodyBuf io.ReadCloser
1498 if body != "" {
1499 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1501 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1502 req.Header = st.header
1504 state := &sessions.SessionState{
1505 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1506 err = proxy.SaveSession(st.rw, req, state)
1507 if err != nil {
1508 return err
1510 for _, c := range st.rw.Result().Cookies() {
1511 req.AddCookie(c)
1513 // This is used by the upstream to validate the signature.
1514 st.authenticator.auth = hmacauth.NewHmacAuth(
1515 crypto.SHA1, []byte(key), upstream.SignatureHeader, upstream.SignatureHeaders)
1516 proxy.ServeHTTP(st.rw, req)
1518 return nil
1521func TestRequestSignature(t *testing.T) {
1522 testCases := map[string]struct {
1523 method string
1524 body string
1525 key string
1526 resp string
1527 }{
1528 "No request signature": {
1529 method: "GET",
1530 body: "",
1531 key: "",
1532 resp: "no signature received",
1533 },
1534 "Get request": {
1535 method: "GET",
1536 body: "",
1537 key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1538 resp: "signatures match",
1539 },
1540 "Post request": {
1541 method: "POST",
1542 body: `{ "hello": "world!" }`,
1543 key: "d90df39e2d19282840252612dd7c81421a372f61",
1544 resp: "signatures match",
1545 },
1547 for name, tc := range testCases {
1548 t.Run(name, func(t *testing.T) {
1549 st, err := NewSignatureTest()
1550 if err != nil {
1551 t.Fatal(err)
1553 t.Cleanup(st.Close)
1554 if tc.key != "" {
1555 st.opts.SignatureKey = fmt.Sprintf("sha1:%s", tc.key)
1557 err = st.MakeRequestWithExpectedKey(tc.method, tc.body, tc.key)
1558 assert.NoError(t, err)
1559 assert.Equal(t, 200, st.rw.Code)
1560 assert.Equal(t, tc.resp, st.rw.Body.String())
1561 })
1565type ajaxRequestTest struct {
1566 opts *options.Options
1567 proxy *OAuthProxy
1570func newAjaxRequestTest(forceJSONErrors bool) (*ajaxRequestTest, error) {
1571 test := &ajaxRequestTest{}
1572 test.opts = baseTestOptions()
1573 test.opts.ForceJSONErrors = forceJSONErrors
1574 err := validation.Validate(test.opts)
1575 if err != nil {
1576 return nil, err
1579 test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
1580 return true
1581 })
1582 if err != nil {
1583 return nil, err
1585 return test, nil
1588func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, []byte, error) {
1589 rw := httptest.NewRecorder()
1590 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1591 if err != nil {
1592 return 0, nil, nil, err
1594 req.Header = header
1595 test.proxy.ServeHTTP(rw, req)
1596 return rw.Code, rw.Header(), rw.Body.Bytes(), nil
1599func testAjaxUnauthorizedRequest(t *testing.T, header http.Header, forceJSONErrors bool) {
1600 test, err := newAjaxRequestTest(forceJSONErrors)
1601 if err != nil {
1602 t.Fatal(err)
1604 endpoint := "/test"
1606 code, rh, body, err := test.getEndpoint(endpoint, header)
1607 assert.NoError(t, err)
1608 assert.Equal(t, http.StatusUnauthorized, code)
1609 mime := rh.Get("Content-Type")
1610 assert.Equal(t, applicationJSON, mime)
1611 assert.Equal(t, []byte("{}"), body)
1613func TestAjaxUnauthorizedRequest1(t *testing.T) {
1614 header := make(http.Header)
1615 header.Add("accept", applicationJSON)
1617 testAjaxUnauthorizedRequest(t, header, false)
1620func TestAjaxUnauthorizedRequest2(t *testing.T) {
1621 header := make(http.Header)
1622 header.Add("Accept", applicationJSON)
1624 testAjaxUnauthorizedRequest(t, header, false)
1627func TestAjaxUnauthorizedRequestAccept1(t *testing.T) {
1628 header := make(http.Header)
1629 header.Add("Accept", "application/json, text/plain, */*")
1631 testAjaxUnauthorizedRequest(t, header, false)
1634func TestForceJSONErrorsUnauthorizedRequest(t *testing.T) {
1635 testAjaxUnauthorizedRequest(t, nil, true)
1638func TestAjaxForbiddendRequest(t *testing.T) {
1639 test, err := newAjaxRequestTest(false)
1640 if err != nil {
1641 t.Fatal(err)
1643 endpoint := "/test"
1644 header := make(http.Header)
1645 code, rh, _, err := test.getEndpoint(endpoint, header)
1646 assert.NoError(t, err)
1647 assert.Equal(t, http.StatusForbidden, code)
1648 mime := rh.Get("Content-Type")
1649 assert.NotEqual(t, applicationJSON, mime)
1652func TestClearSplitCookie(t *testing.T) {
1653 opts := baseTestOptions()
1654 opts.Cookie.Secret = base64CookieSecret
1655 opts.Cookie.Name = "oauth2"
1656 opts.Cookie.Domains = []string{"abc"}
1657 err := validation.Validate(opts)
1658 assert.NoError(t, err)
1660 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1661 if err != nil {
1662 t.Fatal(err)
1665 p := OAuthProxy{CookieOptions: &opts.Cookie, sessionStore: store}
1666 var rw = httptest.NewRecorder()
1667 req := httptest.NewRequest("get", "/", nil)
1669 req.AddCookie(&http.Cookie{
1670 Name: "test1",
1671 Value: "test1",
1672 })
1673 req.AddCookie(&http.Cookie{
1674 Name: "oauth2_0",
1675 Value: "oauth2_0",
1676 })
1677 req.AddCookie(&http.Cookie{
1678 Name: "oauth2_1",
1679 Value: "oauth2_1",
1680 })
1682 err = p.ClearSessionCookie(rw, req)
1683 assert.NoError(t, err)
1684 header := rw.Header()
1686 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1689func TestClearSingleCookie(t *testing.T) {
1690 opts := baseTestOptions()
1691 opts.Cookie.Name = "oauth2"
1692 opts.Cookie.Domains = []string{"abc"}
1693 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1694 if err != nil {
1695 t.Fatal(err)
1698 p := OAuthProxy{CookieOptions: &opts.Cookie, sessionStore: store}
1699 var rw = httptest.NewRecorder()
1700 req := httptest.NewRequest("get", "/", nil)
1702 req.AddCookie(&http.Cookie{
1703 Name: "test1",
1704 Value: "test1",
1705 })
1706 req.AddCookie(&http.Cookie{
1707 Name: "oauth2",
1708 Value: "oauth2",
1709 })
1711 err = p.ClearSessionCookie(rw, req)
1712 assert.NoError(t, err)
1713 header := rw.Header()
1715 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1718type NoOpKeySet struct {
1721func (NoOpKeySet) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
1722 splitStrings := strings.Split(jwt, ".")
1723 payloadString := splitStrings[1]
1724 return base64.RawURLEncoding.DecodeString(payloadString)
1727func TestGetJwtSession(t *testing.T) {
1728 /* token payload:
1730 "sub": "1234567890",
1731 "aud": "https://test.myapp.com",
1732 "name": "John Doe",
1733 "email": "john@example.com",
1734 "iss": "https://issuer.example.com",
1735 "iat": 1553691215,
1736 "exp": 1912151821
1738 */
1739 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1740 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1741 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1742 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1743 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1744 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1746 keyset := NoOpKeySet{}
1747 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1748 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true,
1749 SkipClientIDCheck: true})
1750 verificationOptions := internaloidc.IDTokenVerificationOptions{
1751 AudienceClaims: []string{"aud"},
1752 ClientID: "https://test.myapp.com",
1753 ExtraAudiences: []string{},
1755 internalVerifier := internaloidc.NewVerifier(verifier, verificationOptions)
1757 test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
1758 opts.InjectRequestHeaders = []options.Header{
1760 Name: "Authorization",
1761 Values: []options.HeaderValue{
1763 ClaimSource: &options.ClaimSource{
1764 Claim: "id_token",
1765 Prefix: "Bearer ",
1766 },
1767 },
1768 },
1769 },
1771 Name: "X-Forwarded-User",
1772 Values: []options.HeaderValue{
1774 ClaimSource: &options.ClaimSource{
1775 Claim: "user",
1776 },
1777 },
1778 },
1779 },
1781 Name: "X-Forwarded-Email",
1782 Values: []options.HeaderValue{
1784 ClaimSource: &options.ClaimSource{
1785 Claim: "email",
1786 },
1787 },
1788 },
1789 },
1792 opts.InjectResponseHeaders = []options.Header{
1794 Name: "Authorization",
1795 Values: []options.HeaderValue{
1797 ClaimSource: &options.ClaimSource{
1798 Claim: "id_token",
1799 Prefix: "Bearer ",
1800 },
1801 },
1802 },
1803 },
1805 Name: "X-Auth-Request-User",
1806 Values: []options.HeaderValue{
1808 ClaimSource: &options.ClaimSource{
1809 Claim: "user",
1810 },
1811 },
1812 },
1813 },
1815 Name: "X-Auth-Request-Email",
1816 Values: []options.HeaderValue{
1818 ClaimSource: &options.ClaimSource{
1819 Claim: "email",
1820 },
1821 },
1822 },
1823 },
1825 opts.SkipJwtBearerTokens = true
1826 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), internalVerifier))
1827 })
1828 if err != nil {
1829 t.Fatal(err)
1831 tp, _ := test.proxy.provider.(*TestProvider)
1832 tp.GroupValidator = func(s string) bool {
1833 return true
1836 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1837 test.req.Header = map[string][]string{
1838 "Authorization": {authHeader},
1841 test.proxy.ServeHTTP(test.rw, test.req)
1842 if test.rw.Code >= 400 {
1843 t.Fatalf("expected 3xx got %d", test.rw.Code)
1846 // Check PassAuthorization, should overwrite Basic header
1847 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
1848 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "1234567890")
1849 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
1851 // SetAuthorization and SetXAuthRequest
1852 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
1853 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "1234567890")
1854 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
1857func Test_prepareNoCache(t *testing.T) {
1858 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1859 prepareNoCache(w)
1860 })
1861 mux := http.NewServeMux()
1862 mux.Handle("/", handler)
1864 rec := httptest.NewRecorder()
1865 req := httptest.NewRequest(http.MethodGet, "/", nil)
1866 mux.ServeHTTP(rec, req)
1868 for k, v := range noCacheHeaders {
1869 assert.Equal(t, rec.Header().Get(k), v)
1873func Test_noCacheHeaders(t *testing.T) {
1874 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1875 _, err := w.Write([]byte("upstream"))
1876 if err != nil {
1877 t.Error(err)
1879 }))
1880 t.Cleanup(upstreamServer.Close)
1882 opts := baseTestOptions()
1883 opts.UpstreamServers = options.UpstreamConfig{
1884 Upstreams: []options.Upstream{
1886 ID: upstreamServer.URL,
1887 Path: "/",
1888 URI: upstreamServer.URL,
1889 },
1890 },
1892 opts.SkipAuthRegex = []string{".*"}
1893 err := validation.Validate(opts)
1894 assert.NoError(t, err)
1895 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
1896 if err != nil {
1897 t.Fatal(err)
1900 t.Run("not exist in response from upstream", func(t *testing.T) {
1901 rec := httptest.NewRecorder()
1902 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
1903 proxy.ServeHTTP(rec, req)
1905 assert.Equal(t, http.StatusOK, rec.Code)
1906 assert.Equal(t, "upstream", rec.Body.String())
1908 // checking noCacheHeaders does not exists in response headers from upstream
1909 for k := range noCacheHeaders {
1910 assert.Equal(t, "", rec.Header().Get(k))
1912 })
1914 t.Run("has no-cache", func(t *testing.T) {
1915 tests := []struct {
1916 path string
1917 hasNoCache bool
1918 }{
1920 path: "/oauth2/sign_in",
1921 hasNoCache: true,
1922 },
1924 path: "/oauth2/sign_out",
1925 hasNoCache: true,
1926 },
1928 path: "/oauth2/start",
1929 hasNoCache: true,
1930 },
1932 path: "/oauth2/callback",
1933 hasNoCache: true,
1934 },
1936 path: "/oauth2/auth",
1937 hasNoCache: false,
1938 },
1940 path: "/oauth2/userinfo",
1941 hasNoCache: true,
1942 },
1944 path: "/upstream",
1945 hasNoCache: false,
1946 },
1949 for _, tt := range tests {
1950 t.Run(tt.path, func(t *testing.T) {
1951 rec := httptest.NewRecorder()
1952 req := httptest.NewRequest(http.MethodGet, tt.path, nil)
1953 proxy.ServeHTTP(rec, req)
1954 cacheControl := rec.Result().Header.Get("Cache-Control")
1955 if tt.hasNoCache != (strings.Contains(cacheControl, "no-cache")) {
1956 t.Errorf(`unexpected "Cache-Control" header: %s`, cacheControl)
1958 })
1961 })
1964func baseTestOptions() *options.Options {
1965 opts := options.NewOptions()
1966 opts.Cookie.Secret = rawCookieSecret
1967 opts.Providers[0].ID = "providerID"
1968 opts.Providers[0].ClientID = clientID
1969 opts.Providers[0].ClientSecret = clientSecret
1970 opts.EmailDomains = []string{"*"}
1972 // Default injected headers for legacy configuration
1973 opts.InjectRequestHeaders = []options.Header{
1975 Name: "Authorization",
1976 Values: []options.HeaderValue{
1978 ClaimSource: &options.ClaimSource{
1979 Claim: "user",
1980 BasicAuthPassword: &options.SecretSource{
1981 Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
1982 },
1983 },
1984 },
1985 },
1986 },
1988 Name: "X-Forwarded-User",
1989 Values: []options.HeaderValue{
1991 ClaimSource: &options.ClaimSource{
1992 Claim: "user",
1993 },
1994 },
1995 },
1996 },
1998 Name: "X-Forwarded-Email",
1999 Values: []options.HeaderValue{
2001 ClaimSource: &options.ClaimSource{
2002 Claim: "email",
2003 },
2004 },
2005 },
2006 },
2009 return opts
2012func TestTrustedIPs(t *testing.T) {
2013 tests := []struct {
2014 name string
2015 trustedIPs []string
2016 reverseProxy bool
2017 realClientIPHeader string
2018 req *http.Request
2019 expectTrusted bool
2020 }{
2021 // Check unconfigured behavior.
2023 name: "Default",
2024 trustedIPs: nil,
2025 reverseProxy: false,
2026 realClientIPHeader: "X-Real-IP", // Default value
2027 req: func() *http.Request {
2028 req, _ := http.NewRequest("GET", "/", nil)
2029 return req
2030 }(),
2031 expectTrusted: false,
2032 },
2033 // Check using req.RemoteAddr (Options.ReverseProxy == false).
2035 name: "WithRemoteAddr",
2036 trustedIPs: []string{"127.0.0.1"},
2037 reverseProxy: false,
2038 realClientIPHeader: "X-Real-IP", // Default value
2039 req: func() *http.Request {
2040 req, _ := http.NewRequest("GET", "/", nil)
2041 req.RemoteAddr = "127.0.0.1:43670"
2042 return req
2043 }(),
2044 expectTrusted: true,
2045 },
2046 // Check ignores req.RemoteAddr match when behind a reverse proxy / missing header.
2048 name: "IgnoresRemoteAddrInReverseProxyMode",
2049 trustedIPs: []string{"127.0.0.1"},
2050 reverseProxy: true,
2051 realClientIPHeader: "X-Real-IP", // Default value
2052 req: func() *http.Request {
2053 req, _ := http.NewRequest("GET", "/", nil)
2054 req.RemoteAddr = "127.0.0.1:44324"
2055 return req
2056 }(),
2057 expectTrusted: false,
2058 },
2059 // Check successful trusting of localhost in IPv4.
2061 name: "TrustsLocalhostInReverseProxyMode",
2062 trustedIPs: []string{"127.0.0.0/8", "::1"},
2063 reverseProxy: true,
2064 realClientIPHeader: "X-Forwarded-For",
2065 req: func() *http.Request {
2066 req, _ := http.NewRequest("GET", "/", nil)
2067 req.Header.Add("X-Forwarded-For", "127.0.0.1")
2068 return req
2069 }(),
2070 expectTrusted: true,
2071 },
2072 // Check successful trusting of localhost in IPv6.
2074 name: "TrustsIP6LocalostInReverseProxyMode",
2075 trustedIPs: []string{"127.0.0.0/8", "::1"},
2076 reverseProxy: true,
2077 realClientIPHeader: "X-Forwarded-For",
2078 req: func() *http.Request {
2079 req, _ := http.NewRequest("GET", "/", nil)
2080 req.Header.Add("X-Forwarded-For", "::1")
2081 return req
2082 }(),
2083 expectTrusted: true,
2084 },
2085 // Check does not trust random IPv4 address.
2087 name: "DoesNotTrustRandomIP4Address",
2088 trustedIPs: []string{"127.0.0.0/8", "::1"},
2089 reverseProxy: true,
2090 realClientIPHeader: "X-Forwarded-For",
2091 req: func() *http.Request {
2092 req, _ := http.NewRequest("GET", "/", nil)
2093 req.Header.Add("X-Forwarded-For", "12.34.56.78")
2094 return req
2095 }(),
2096 expectTrusted: false,
2097 },
2098 // Check does not trust random IPv6 address.
2100 name: "DoesNotTrustRandomIP6Address",
2101 trustedIPs: []string{"127.0.0.0/8", "::1"},
2102 reverseProxy: true,
2103 realClientIPHeader: "X-Forwarded-For",
2104 req: func() *http.Request {
2105 req, _ := http.NewRequest("GET", "/", nil)
2106 req.Header.Add("X-Forwarded-For", "::2")
2107 return req
2108 }(),
2109 expectTrusted: false,
2110 },
2111 // Check respects correct header.
2113 name: "RespectsCorrectHeaderInReverseProxyMode",
2114 trustedIPs: []string{"127.0.0.0/8", "::1"},
2115 reverseProxy: true,
2116 realClientIPHeader: "X-Forwarded-For",
2117 req: func() *http.Request {
2118 req, _ := http.NewRequest("GET", "/", nil)
2119 req.Header.Add("X-Real-IP", "::1")
2120 return req
2121 }(),
2122 expectTrusted: false,
2123 },
2124 // Check doesn't trust if garbage is provided.
2126 name: "DoesNotTrustGarbageInReverseProxyMode",
2127 trustedIPs: []string{"127.0.0.0/8", "::1"},
2128 reverseProxy: true,
2129 realClientIPHeader: "X-Forwarded-For",
2130 req: func() *http.Request {
2131 req, _ := http.NewRequest("GET", "/", nil)
2132 req.Header.Add("X-Forwarded-For", "adsfljk29242as!!")
2133 return req
2134 }(),
2135 expectTrusted: false,
2136 },
2137 // Check doesn't trust if garbage is provided (no reverse-proxy).
2139 name: "DoesNotTrustGarbage",
2140 trustedIPs: []string{"127.0.0.0/8", "::1"},
2141 reverseProxy: false,
2142 realClientIPHeader: "X-Real-IP",
2143 req: func() *http.Request {
2144 req, _ := http.NewRequest("GET", "/", nil)
2145 req.RemoteAddr = "adsfljk29242as!!"
2146 return req
2147 }(),
2148 expectTrusted: false,
2149 },
2152 for _, tt := range tests {
2153 t.Run(tt.name, func(t *testing.T) {
2154 opts := baseTestOptions()
2155 opts.UpstreamServers = options.UpstreamConfig{
2156 Upstreams: []options.Upstream{
2158 ID: "static",
2159 Path: "/",
2160 Static: true,
2161 },
2162 },
2164 opts.TrustedIPs = tt.trustedIPs
2165 opts.ReverseProxy = tt.reverseProxy
2166 opts.RealClientIPHeader = tt.realClientIPHeader
2167 err := validation.Validate(opts)
2168 assert.NoError(t, err)
2170 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
2171 assert.NoError(t, err)
2172 rw := httptest.NewRecorder()
2174 proxy.ServeHTTP(rw, tt.req)
2175 if tt.expectTrusted {
2176 assert.Equal(t, 200, rw.Code)
2177 } else {
2178 assert.Equal(t, 403, rw.Code)
2180 })
2184func Test_buildRoutesAllowlist(t *testing.T) {
2185 type expectedAllowedRoute struct {
2186 method string
2187 regexString string
2190 testCases := []struct {
2191 name string
2192 skipAuthRegex []string
2193 skipAuthRoutes []string
2194 expectedRoutes []expectedAllowedRoute
2195 shouldError bool
2196 }{
2198 name: "No skip auth configured",
2199 skipAuthRegex: []string{},
2200 skipAuthRoutes: []string{},
2201 expectedRoutes: []expectedAllowedRoute{},
2202 shouldError: false,
2203 },
2205 name: "Only skipAuthRegex configured",
2206 skipAuthRegex: []string{
2207 "^/foo/bar",
2208 "^/baz/[0-9]+/thing",
2209 },
2210 skipAuthRoutes: []string{},
2211 expectedRoutes: []expectedAllowedRoute{
2213 method: "",
2214 regexString: "^/foo/bar",
2215 },
2217 method: "",
2218 regexString: "^/baz/[0-9]+/thing",
2219 },
2220 },
2221 shouldError: false,
2222 },
2224 name: "Only skipAuthRoutes configured",
2225 skipAuthRegex: []string{},
2226 skipAuthRoutes: []string{
2227 "GET=^/foo/bar",
2228 "POST=^/baz/[0-9]+/thing",
2229 "^/all/methods$",
2230 "WEIRD=^/methods/are/allowed",
2231 "PATCH=/second/equals?are=handled&just=fine",
2232 },
2233 expectedRoutes: []expectedAllowedRoute{
2235 method: "GET",
2236 regexString: "^/foo/bar",
2237 },
2239 method: "POST",
2240 regexString: "^/baz/[0-9]+/thing",
2241 },
2243 method: "",
2244 regexString: "^/all/methods$",
2245 },
2247 method: "WEIRD",
2248 regexString: "^/methods/are/allowed",
2249 },
2251 method: "PATCH",
2252 regexString: "/second/equals?are=handled&just=fine",
2253 },
2254 },
2255 shouldError: false,
2256 },
2258 name: "Both skipAuthRegexes and skipAuthRoutes configured",
2259 skipAuthRegex: []string{
2260 "^/foo/bar/regex",
2261 "^/baz/[0-9]+/thing/regex",
2262 },
2263 skipAuthRoutes: []string{
2264 "GET=^/foo/bar",
2265 "POST=^/baz/[0-9]+/thing",
2266 "^/all/methods$",
2267 },
2268 expectedRoutes: []expectedAllowedRoute{
2270 method: "",
2271 regexString: "^/foo/bar/regex",
2272 },
2274 method: "",
2275 regexString: "^/baz/[0-9]+/thing/regex",
2276 },
2278 method: "GET",
2279 regexString: "^/foo/bar",
2280 },
2282 method: "POST",
2283 regexString: "^/baz/[0-9]+/thing",
2284 },
2286 method: "",
2287 regexString: "^/all/methods$",
2288 },
2289 },
2290 shouldError: false,
2291 },
2293 name: "Invalid skipAuthRegex entry",
2294 skipAuthRegex: []string{
2295 "^/foo/bar",
2296 "^/baz/[0-9]+/thing",
2297 "(bad[regex",
2298 },
2299 skipAuthRoutes: []string{},
2300 expectedRoutes: []expectedAllowedRoute{},
2301 shouldError: true,
2302 },
2304 name: "Invalid skipAuthRoutes entry",
2305 skipAuthRegex: []string{},
2306 skipAuthRoutes: []string{
2307 "GET=^/foo/bar",
2308 "POST=^/baz/[0-9]+/thing",
2309 "^/all/methods$",
2310 "PUT=(bad[regex",
2311 },
2312 expectedRoutes: []expectedAllowedRoute{},
2313 shouldError: true,
2314 },
2317 for _, tc := range testCases {
2318 t.Run(tc.name, func(t *testing.T) {
2319 opts := &options.Options{
2320 SkipAuthRegex: tc.skipAuthRegex,
2321 SkipAuthRoutes: tc.skipAuthRoutes,
2323 routes, err := buildRoutesAllowlist(opts)
2324 if tc.shouldError {
2325 assert.Error(t, err)
2326 return
2328 assert.NoError(t, err)
2330 for i, route := range routes {
2331 assert.Greater(t, len(tc.expectedRoutes), i)
2332 assert.Equal(t, route.method, tc.expectedRoutes[i].method)
2333 assert.Equal(t, route.pathRegex.String(), tc.expectedRoutes[i].regexString)
2335 })
2339func TestAllowedRequest(t *testing.T) {
2340 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2341 w.WriteHeader(200)
2342 _, err := w.Write([]byte("Allowed Request"))
2343 if err != nil {
2344 t.Fatal(err)
2346 }))
2347 t.Cleanup(upstreamServer.Close)
2349 opts := baseTestOptions()
2350 opts.UpstreamServers = options.UpstreamConfig{
2351 Upstreams: []options.Upstream{
2353 ID: upstreamServer.URL,
2354 Path: "/",
2355 URI: upstreamServer.URL,
2356 },
2357 },
2359 opts.SkipAuthRegex = []string{
2360 "^/skip/auth/regex$",
2362 opts.SkipAuthRoutes = []string{
2363 "GET=^/skip/auth/routes/get",
2365 err := validation.Validate(opts)
2366 assert.NoError(t, err)
2367 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2368 if err != nil {
2369 t.Fatal(err)
2372 testCases := []struct {
2373 name string
2374 method string
2375 url string
2376 allowed bool
2377 }{
2379 name: "Regex GET allowed",
2380 method: "GET",
2381 url: "/skip/auth/regex",
2382 allowed: true,
2383 },
2385 name: "Regex POST allowed ",
2386 method: "POST",
2387 url: "/skip/auth/regex",
2388 allowed: true,
2389 },
2391 name: "Regex denied",
2392 method: "GET",
2393 url: "/wrong/denied",
2394 allowed: false,
2395 },
2397 name: "Route allowed",
2398 method: "GET",
2399 url: "/skip/auth/routes/get",
2400 allowed: true,
2401 },
2403 name: "Route denied with wrong method",
2404 method: "PATCH",
2405 url: "/skip/auth/routes/get",
2406 allowed: false,
2407 },
2409 name: "Route denied with wrong path",
2410 method: "GET",
2411 url: "/skip/auth/routes/wrong/path",
2412 allowed: false,
2413 },
2416 for _, tc := range testCases {
2417 t.Run(tc.name, func(t *testing.T) {
2418 req, err := http.NewRequest(tc.method, tc.url, nil)
2419 assert.NoError(t, err)
2420 assert.Equal(t, tc.allowed, proxy.isAllowedRoute(req))
2422 rw := httptest.NewRecorder()
2423 proxy.ServeHTTP(rw, req)
2425 if tc.allowed {
2426 assert.Equal(t, 200, rw.Code)
2427 assert.Equal(t, "Allowed Request", rw.Body.String())
2428 } else {
2429 assert.Equal(t, 403, rw.Code)
2431 })
2435func TestProxyAllowedGroups(t *testing.T) {
2436 tests := []struct {
2437 name string
2438 allowedGroups []string
2439 groups []string
2440 expectUnauthorized bool
2441 }{
2442 {"NoAllowedGroups", []string{}, []string{}, false},
2443 {"NoAllowedGroupsUserHasGroups", []string{}, []string{"a", "b"}, false},
2444 {"UserInAllowedGroup", []string{"a"}, []string{"a", "b"}, false},
2445 {"UserNotInAllowedGroup", []string{"a"}, []string{"c"}, true},
2448 for _, tt := range tests {
2449 t.Run(tt.name, func(t *testing.T) {
2450 emailAddress := "test"
2451 created := time.Now()
2453 session := &sessions.SessionState{
2454 Groups: tt.groups,
2455 Email: emailAddress,
2456 AccessToken: "oauth_token",
2457 CreatedAt: &created,
2460 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2461 w.WriteHeader(200)
2462 }))
2463 t.Cleanup(upstreamServer.Close)
2465 test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
2466 opts.Providers[0].AllowedGroups = tt.allowedGroups
2467 opts.UpstreamServers = options.UpstreamConfig{
2468 Upstreams: []options.Upstream{
2470 ID: upstreamServer.URL,
2471 Path: "/",
2472 URI: upstreamServer.URL,
2473 },
2474 },
2476 })
2477 if err != nil {
2478 t.Fatal(err)
2481 test.req, _ = http.NewRequest("GET", "/", nil)
2483 test.req.Header.Add("accept", applicationJSON)
2484 err = test.SaveSession(session)
2485 assert.NoError(t, err)
2486 test.proxy.ServeHTTP(test.rw, test.req)
2488 if tt.expectUnauthorized {
2489 assert.Equal(t, http.StatusForbidden, test.rw.Code)
2490 } else {
2491 assert.Equal(t, http.StatusOK, test.rw.Code)
2493 })
2497func TestAuthOnlyAllowedGroups(t *testing.T) {
2498 testCases := []struct {
2499 name string
2500 allowedGroups []string
2501 groups []string
2502 querystring string
2503 expectedStatusCode int
2504 }{
2506 name: "NoAllowedGroups",
2507 allowedGroups: []string{},
2508 groups: []string{},
2509 querystring: "",
2510 expectedStatusCode: http.StatusAccepted,
2511 },
2513 name: "NoAllowedGroupsUserHasGroups",
2514 allowedGroups: []string{},
2515 groups: []string{"a", "b"},
2516 querystring: "",
2517 expectedStatusCode: http.StatusAccepted,
2518 },
2520 name: "UserInAllowedGroup",
2521 allowedGroups: []string{"a"},
2522 groups: []string{"a", "b"},
2523 querystring: "",
2524 expectedStatusCode: http.StatusAccepted,
2525 },
2527 name: "UserNotInAllowedGroup",
2528 allowedGroups: []string{"a"},
2529 groups: []string{"c"},
2530 querystring: "",
2531 expectedStatusCode: http.StatusUnauthorized,
2532 },
2534 name: "UserInQuerystringGroup",
2535 allowedGroups: []string{"a", "b"},
2536 groups: []string{"a", "c"},
2537 querystring: "?allowed_groups=a",
2538 expectedStatusCode: http.StatusAccepted,
2539 },
2541 name: "UserInMultiParamQuerystringGroup",
2542 allowedGroups: []string{"a", "b"},
2543 groups: []string{"b"},
2544 querystring: "?allowed_groups=a&allowed_groups=b,d",
2545 expectedStatusCode: http.StatusAccepted,
2546 },
2548 name: "UserInOnlyQuerystringGroup",
2549 allowedGroups: []string{},
2550 groups: []string{"a", "c"},
2551 querystring: "?allowed_groups=a,b",
2552 expectedStatusCode: http.StatusAccepted,
2553 },
2555 name: "UserInDelimitedQuerystringGroup",
2556 allowedGroups: []string{"a", "b", "c"},
2557 groups: []string{"c"},
2558 querystring: "?allowed_groups=a,c",
2559 expectedStatusCode: http.StatusAccepted,
2560 },
2562 name: "UserNotInQuerystringGroup",
2563 allowedGroups: []string{},
2564 groups: []string{"c"},
2565 querystring: "?allowed_groups=a,b",
2566 expectedStatusCode: http.StatusForbidden,
2567 },
2569 name: "UserInConfigGroupNotInQuerystringGroup",
2570 allowedGroups: []string{"a", "b", "c"},
2571 groups: []string{"c"},
2572 querystring: "?allowed_groups=a,b",
2573 expectedStatusCode: http.StatusForbidden,
2574 },
2576 name: "UserInQuerystringGroupNotInConfigGroup",
2577 allowedGroups: []string{"a", "b"},
2578 groups: []string{"c"},
2579 querystring: "?allowed_groups=b,c",
2580 expectedStatusCode: http.StatusUnauthorized,
2581 },
2584 for _, tc := range testCases {
2585 t.Run(tc.name, func(t *testing.T) {
2586 emailAddress := "test"
2587 created := time.Now()
2589 session := &sessions.SessionState{
2590 Groups: tc.groups,
2591 Email: emailAddress,
2592 AccessToken: "oauth_token",
2593 CreatedAt: &created,
2596 test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {
2597 opts.Providers[0].AllowedGroups = tc.allowedGroups
2598 })
2599 if err != nil {
2600 t.Fatal(err)
2603 err = test.SaveSession(session)
2604 assert.NoError(t, err)
2606 test.proxy.ServeHTTP(test.rw, test.req)
2608 assert.Equal(t, tc.expectedStatusCode, test.rw.Code)
2609 })
2613func TestAuthOnlyAllowedGroupsWithSkipMethods(t *testing.T) {
2614 testCases := []struct {
2615 name string
2616 groups []string
2617 method string
2618 ip string
2619 withSession bool
2620 expectedStatusCode int
2621 }{
2623 name: "UserWithGroupSkipAuthPreflight",
2624 groups: []string{"a", "c"},
2625 method: "OPTIONS",
2626 ip: "1.2.3.5:43670",
2627 withSession: true,
2628 expectedStatusCode: http.StatusAccepted,
2629 },
2631 name: "UserWithGroupTrustedIp",
2632 groups: []string{"a", "c"},
2633 method: "GET",
2634 ip: "1.2.3.4:43670",
2635 withSession: true,
2636 expectedStatusCode: http.StatusAccepted,
2637 },
2639 name: "UserWithoutGroupSkipAuthPreflight",
2640 groups: []string{"c"},
2641 method: "OPTIONS",
2642 ip: "1.2.3.5:43670",
2643 withSession: true,
2644 expectedStatusCode: http.StatusForbidden,
2645 },
2647 name: "UserWithoutGroupTrustedIp",
2648 groups: []string{"c"},
2649 method: "GET",
2650 ip: "1.2.3.4:43670",
2651 withSession: true,
2652 expectedStatusCode: http.StatusForbidden,
2653 },
2655 name: "UserWithoutSessionSkipAuthPreflight",
2656 method: "OPTIONS",
2657 ip: "1.2.3.5:43670",
2658 withSession: false,
2659 expectedStatusCode: http.StatusAccepted,
2660 },
2662 name: "UserWithoutSessionTrustedIp",
2663 method: "GET",
2664 ip: "1.2.3.4:43670",
2665 withSession: false,
2666 expectedStatusCode: http.StatusAccepted,
2667 },
2670 for _, tc := range testCases {
2671 t.Run(tc.name, func(t *testing.T) {
2672 test, err := NewAuthOnlyEndpointTest("?allowed_groups=a,b", func(opts *options.Options) {
2673 opts.SkipAuthPreflight = true
2674 opts.TrustedIPs = []string{"1.2.3.4"}
2675 })
2676 if err != nil {
2677 t.Fatal(err)
2680 test.req.Method = tc.method
2681 test.req.RemoteAddr = tc.ip
2683 if tc.withSession {
2684 created := time.Now()
2685 session := &sessions.SessionState{
2686 Groups: tc.groups,
2687 Email: "test",
2688 AccessToken: "oauth_token",
2689 CreatedAt: &created,
2691 err = test.SaveSession(session)
2693 assert.NoError(t, err)
2695 test.proxy.ServeHTTP(test.rw, test.req)
2697 assert.Equal(t, tc.expectedStatusCode, test.rw.Code)
2698 })
2702func TestAuthOnlyAllowedEmailDomains(t *testing.T) {
2703 testCases := []struct {
2704 name string
2705 email string
2706 querystring string
2707 expectedStatusCode int
2708 }{
2710 name: "NotEmailRestriction",
2711 email: "toto@example.com",
2712 querystring: "",
2713 expectedStatusCode: http.StatusAccepted,
2714 },
2716 name: "UserInAllowedEmailDomain",
2717 email: "toto@example.com",
2718 querystring: "?allowed_email_domains=example.com",
2719 expectedStatusCode: http.StatusAccepted,
2720 },
2722 name: "UserNotInAllowedEmailDomain",
2723 email: "toto@example.com",
2724 querystring: "?allowed_email_domains=a.example.com",
2725 expectedStatusCode: http.StatusForbidden,
2726 },
2728 name: "UserNotInAllowedEmailDomains",
2729 email: "toto@example.com",
2730 querystring: "?allowed_email_domains=a.example.com,b.example.com",
2731 expectedStatusCode: http.StatusForbidden,
2732 },
2734 name: "UserInAllowedEmailDomains",
2735 email: "toto@example.com",
2736 querystring: "?allowed_email_domains=a.example.com,example.com",
2737 expectedStatusCode: http.StatusAccepted,
2738 },
2740 name: "UserInAllowedEmailDomainWildcard",
2741 email: "toto@foo.example.com",
2742 querystring: "?allowed_email_domains=*.example.com",
2743 expectedStatusCode: http.StatusAccepted,
2744 },
2746 name: "UserNotInAllowedEmailDomainWildcard",
2747 email: "toto@example.com",
2748 querystring: "?allowed_email_domains=*.a.example.com",
2749 expectedStatusCode: http.StatusForbidden,
2750 },
2752 name: "UserInAllowedEmailDomainsWildcard",
2753 email: "toto@example.com",
2754 querystring: "?allowed_email_domains=*.a.example.com,*.b.example.com",
2755 expectedStatusCode: http.StatusForbidden,
2756 },
2758 name: "UserInAllowedEmailDomainsWildcard",
2759 email: "toto@c.example.com",
2760 querystring: "?allowed_email_domains=a.b.c.example.com,*.c.example.com",
2761 expectedStatusCode: http.StatusAccepted,
2762 },
2765 for _, tc := range testCases {
2766 t.Run(tc.name, func(t *testing.T) {
2767 groups := []string{}
2769 created := time.Now()
2771 session := &sessions.SessionState{
2772 Groups: groups,
2773 Email: tc.email,
2774 AccessToken: "oauth_token",
2775 CreatedAt: &created,
2778 test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {})
2779 if err != nil {
2780 t.Fatal(err)
2783 err = test.SaveSession(session)
2784 assert.NoError(t, err)
2786 test.proxy.ServeHTTP(test.rw, test.req)
2788 assert.Equal(t, tc.expectedStatusCode, test.rw.Code)
2789 })
2793func TestAuthOnlyAllowedEmails(t *testing.T) {
2794 testCases := []struct {
2795 name string
2796 email string
2797 querystring string
2798 expectedStatusCode int
2799 }{
2801 name: "NotEmailRestriction",
2802 email: "toto@example.com",
2803 querystring: "",
2804 expectedStatusCode: http.StatusAccepted,
2805 },
2807 name: "UserInAllowedEmail",
2808 email: "toto@example.com",
2809 querystring: "?allowed_emails=toto@example.com",
2810 expectedStatusCode: http.StatusAccepted,
2811 },
2813 name: "UserNotInAllowedEmail",
2814 email: "toto@example.com",
2815 querystring: "?allowed_emails=tete@example.com",
2816 expectedStatusCode: http.StatusForbidden,
2817 },
2819 name: "UserNotInAllowedEmails",
2820 email: "toto@example.com",
2821 querystring: "?allowed_emails=tete@example.com,tutu@example.com",
2822 expectedStatusCode: http.StatusForbidden,
2823 },
2825 name: "UserInAllowedEmails",
2826 email: "toto@example.com",
2827 querystring: "?allowed_emails=tete@example.com,toto@example.com",
2828 expectedStatusCode: http.StatusAccepted,
2829 },
2832 for _, tc := range testCases {
2833 t.Run(tc.name, func(t *testing.T) {
2834 groups := []string{}
2836 created := time.Now()
2838 session := &sessions.SessionState{
2839 Groups: groups,
2840 Email: tc.email,
2841 AccessToken: "oauth_token",
2842 CreatedAt: &created,
2845 test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {})
2846 if err != nil {
2847 t.Fatal(err)
2850 err = test.SaveSession(session)
2851 assert.NoError(t, err)
2853 test.proxy.ServeHTTP(test.rw, test.req)
2855 assert.Equal(t, tc.expectedStatusCode, test.rw.Code)
2856 })