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"
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"
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 })
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{
73 err := validation.Validate(opts)
74 assert.NoError(t, err)
76 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
81 testCases := []struct {
88 ExpectedResult: false,
92 Redirect: "/redirect",
97 Redirect: "//redirect",
98 ExpectedResult: false,
102 Redirect: "http://foo.bar/redirect",
103 ExpectedResult: true,
107 Redirect: "https://foo.bar/redirect",
108 ExpectedResult: true,
111 Desc: "invalidHTTPSubdomain",
112 Redirect: "http://baz.foo.bar/redirect",
113 ExpectedResult: false,
116 Desc: "invalidHTTPSSubdomain",
117 Redirect: "https://baz.foo.bar/redirect",
118 ExpectedResult: false,
121 Desc: "validHTTPSubdomain",
122 Redirect: "http://baz.bar.foo/redirect",
123 ExpectedResult: true,
126 Desc: "validHTTPSSubdomain",
127 Redirect: "https://baz.bar.foo/redirect",
128 ExpectedResult: true,
131 Desc: "validHTTPDomain",
132 Redirect: "http://bar.foo/redirect",
133 ExpectedResult: true,
136 Desc: "invalidHTTP1",
137 Redirect: "http://foo.bar.evil.corp/redirect",
138 ExpectedResult: false,
141 Desc: "invalidHTTPS1",
142 Redirect: "https://foo.bar.evil.corp/redirect",
143 ExpectedResult: false,
146 Desc: "invalidHTTP2",
147 Redirect: "http://evil.corp/redirect?rd=foo.bar",
148 ExpectedResult: false,
151 Desc: "invalidHTTPS2",
152 Redirect: "https://evil.corp/redirect?rd=foo.bar",
153 ExpectedResult: false,
157 Redirect: "https://evil.corp:3838/redirect",
158 ExpectedResult: false,
161 Desc: "invalidEmptyPort",
162 Redirect: "http://foo.bar:3838/redirect",
163 ExpectedResult: false,
166 Desc: "invalidEmptyPortSubdomain",
167 Redirect: "http://baz.bar.foo:3838/redirect",
168 ExpectedResult: false,
171 Desc: "validSpecificPort",
172 Redirect: "http://port.bar:8080/redirect",
173 ExpectedResult: true,
176 Desc: "invalidSpecificPort",
177 Redirect: "http://port.bar:3838/redirect",
178 ExpectedResult: false,
181 Desc: "validSpecificPortSubdomain",
182 Redirect: "http://foo.sub.port.bar:8080/redirect",
183 ExpectedResult: true,
186 Desc: "invalidSpecificPortSubdomain",
187 Redirect: "http://foo.sub.port.bar:3838/redirect",
188 ExpectedResult: false,
191 Desc: "validAnyPort1",
192 Redirect: "http://anyport.bar:8080/redirect",
193 ExpectedResult: true,
196 Desc: "validAnyPort2",
197 Redirect: "http://anyport.bar:8081/redirect",
198 ExpectedResult: true,
201 Desc: "validAnyPortSubdomain1",
202 Redirect: "http://a.sub.anyport.bar:8080/redirect",
203 ExpectedResult: true,
206 Desc: "validAnyPortSubdomain2",
207 Redirect: "http://a.sub.anyport.bar:8081/redirect",
208 ExpectedResult: true,
211 Desc: "openRedirect1",
212 Redirect: "/\\evil.com",
213 ExpectedResult: false,
216 Desc: "openRedirectSpace1",
217 Redirect: "/ /evil.com",
218 ExpectedResult: false,
221 Desc: "openRedirectSpace2",
222 Redirect: "/ \\evil.com",
223 ExpectedResult: false,
226 Desc: "openRedirectTab1",
227 Redirect: "/\t/evil.com",
228 ExpectedResult: false,
231 Desc: "openRedirectTab2",
232 Redirect: "/\t\\evil.com",
233 ExpectedResult: false,
236 Desc: "openRedirectVerticalTab1",
237 Redirect: "/\v/evil.com",
238 ExpectedResult: false,
241 Desc: "openRedirectVerticalTab2",
242 Redirect: "/\v\\evil.com",
243 ExpectedResult: false,
246 Desc: "openRedirectNewLine1",
247 Redirect: "/\n/evil.com",
248 ExpectedResult: false,
251 Desc: "openRedirectNewLine2",
252 Redirect: "/\n\\evil.com",
253 ExpectedResult: false,
256 Desc: "openRedirectCarriageReturn1",
257 Redirect: "/\r/evil.com",
258 ExpectedResult: false,
261 Desc: "openRedirectCarriageReturn2",
262 Redirect: "/\r\\evil.com",
263 ExpectedResult: false,
266 Desc: "openRedirectTripleTab",
267 Redirect: "/\t\t/\t/evil.com",
268 ExpectedResult: false,
271 Desc: "openRedirectTripleTab2",
272 Redirect: "/\t\t\\\t/evil.com",
273 ExpectedResult: false,
276 Desc: "openRedirectQuadTab1",
277 Redirect: "/\t\t/\t\t\\evil.com",
278 ExpectedResult: false,
281 Desc: "openRedirectQuadTab2",
282 Redirect: "/\t\t\\\t\t/evil.com",
283 ExpectedResult: false,
286 Desc: "openRedirectPeriod1",
287 Redirect: "/./\\evil.com",
288 ExpectedResult: false,
291 Desc: "openRedirectPeriod2",
292 Redirect: "/./../../\\evil.com",
293 ExpectedResult: false,
296 Desc: "openRedirectDoubleTab",
297 Redirect: "/\t/\t\\evil.com",
298 ExpectedResult: false,
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)
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{
320 ".sub.port.bar:8080",
322 ".sub.anyport.bar:*",
323 "www.whitelisteddomain.tld",
325 err := validation.Validate(opts)
326 assert.NoError(t, err)
328 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
333 file, err := os.Open("./testdata/openredirects.txt")
337 defer func(t *testing.T) {
338 if err := file.Close(); err != nil {
343 scanner := bufio.NewScanner(file)
346 t.Run(rd, func(t *testing.T) {
347 rdUnescaped, err := url.QueryUnescape(rd)
351 if proxy.IsValidRedirect(rdUnescaped) {
352 t.Errorf("Expected %q to not be valid (unescaped: %q)", rd, rdUnescaped)
357 if err := scanner.Err(); err != nil {
362type TestProvider struct {
363 *providers.ProviderData
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",
377 Host: providerURL.Host,
378 Path: "/oauth/authorize",
382 Host: providerURL.Host,
383 Path: "/oauth/token",
385 ProfileURL: &url.URL{
387 Host: providerURL.Host,
388 Path: "/api/v1/profile",
390 Scope: "profile.email",
392 EmailAddress: emailAddress,
393 GroupValidator: func(s string) bool {
399func (tp *TestProvider) GetEmailAddress(_ context.Context, _ *sessions.SessionState) (string, error) {
400 return tp.EmailAddress, nil
403func (tp *TestProvider) ValidateSession(_ context.Context, _ *sessions.SessionState) bool {
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 })
417 _, err = proxy.redeemCode(context.Background(), "www.example.com", "")
421func Test_enrichSession(t *testing.T) {
423 sessionUser = "Mr Session"
424 sessionEmail = "session@example.com"
425 providerEmail = "provider@example.com"
428 testCases := map[string]struct {
429 session *sessions.SessionState
433 "Session already has enrichable fields": {
434 session: &sessions.SessionState{
438 expectedUser: sessionUser,
439 expectedEmail: sessionEmail,
441 "Session is missing Email and GetEmailAddress is implemented": {
442 session: &sessions.SessionState{
445 expectedUser: sessionUser,
446 expectedEmail: providerEmail,
448 "Session is missing User and GetUserName is not implemented": {
449 session: &sessions.SessionState{
453 expectedEmail: sessionEmail,
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 })
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)
479func TestBasicAuthPassword(t *testing.T) {
480 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
481 logger.Printf("%#v", r)
485 payload = `{"access_token": "my_auth_token"}`
487 payload = r.Header.Get("Authorization")
489 payload = "No Authorization header found."
493 _, err := w.Write([]byte(payload))
499 basicAuthPassword := "This is a secure password"
500 opts := baseTestOptions()
501 opts.UpstreamServers = options.Upstreams{
503 ID: providerServer.URL,
505 URI: providerServer.URL,
509 opts.Cookie.Secure = false
510 opts.InjectRequestHeaders = []options.Header{
512 Name: "Authorization",
513 Values: []options.HeaderValue{
515 ClaimSource: &options.ClaimSource{
517 BasicAuthPassword: &options.SecretSource{
518 Value: []byte(basicAuthPassword),
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
540 // Save the required session
541 rw := httptest.NewRecorder()
542 req, _ := http.NewRequest("GET", "/", nil)
543 err = proxy.sessionStore.Save(rw, req, &sessions.SessionState{
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{
567 Name: "X-Forwarded-Groups",
568 Values: []options.HeaderValue{
570 ClaimSource: &options.ClaimSource{
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{
590 AccessToken: "oauth_token",
594 proxy, err := NewOAuthProxy(opts, func(email string) bool {
595 return email == emailAddress
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
619 opts *options.Options
622type PassAccessTokenTestOptions struct {
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) {
635 payload = `{"access_token": "my_auth_token"}`
637 payload = r.Header.Get("X-Forwarded-Access-Token")
639 payload = "No access token found."
643 _, err := w.Write([]byte(payload))
649 patt.opts = baseTestOptions()
650 patt.opts.UpstreamServers = options.Upstreams{
652 ID: patt.providerServer.URL,
654 URI: patt.providerServer.URL,
657 if opts.ProxyUpstream.ID != "" {
658 patt.opts.UpstreamServers = append(patt.opts.UpstreamServers, opts.ProxyUpstream)
661 patt.opts.Cookie.Secure = false
662 if opts.PassAccessToken {
663 patt.opts.InjectRequestHeaders = []options.Header{
665 Name: "X-Forwarded-Access-Token",
666 Values: []options.HeaderValue{
668 ClaimSource: &options.ClaimSource{
669 Claim: "access_token",
677 err := validation.Validate(patt.opts)
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
695func (patTest *PassAccessTokenTest) Close() {
696 patTest.providerServer.Close()
699func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
701 rw := httptest.NewRecorder()
702 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
703 strings.NewReader(""))
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
717 keyPrefix := cookieName + "="
719 for _, field := range strings.Split(cookie, "; ") {
720 value = strings.TrimPrefix(field, keyPrefix)
731 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
735 req.AddCookie(&http.Cookie{
739 Expires: time.Now().Add(time.Duration(24)),
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,
755 t.Cleanup(patTest.Close)
757 // A successful validation will redirect and set the auth cookie.
758 code, cookie := patTest.getCallbackEndpoint()
760 t.Fatalf("expected 302; got %d", code)
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, "/")
769 t.Fatalf("expected 200; got %d", code)
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{
779 Path: "/static-proxy",
786 t.Cleanup(patTest.Close)
788 // A successful validation will redirect and set the auth cookie.
789 code, cookie := patTest.getCallbackEndpoint()
791 t.Fatalf("expected 302; got %d", code)
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")
799 t.Fatalf("expected 200; got %d", code)
801 assert.Equal(t, "Authenticated", payload)
804func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
805 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
806 PassAccessToken: false,
811 t.Cleanup(patTest.Close)
813 // A successful validation will redirect and set the auth cookie.
814 code, cookie := patTest.getCallbackEndpoint()
816 t.Fatalf("expected 302; got %d", code)
818 assert.NotEqual(t, nil, cookie)
820 // Now we make a regular request, but the access token header should
822 code, payload := patTest.getEndpointWithCookie(cookie, "/")
824 t.Fatalf("expected 200; got %d", code)
826 assert.Equal(t, "No access token found.", payload)
829type SignInPageTest struct {
830 opts *options.Options
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)
849 sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
855 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
856 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
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)
873 const endpoint = "/some/random/endpoint"
875 code, body := sipTest.GetEndpoint(endpoint)
876 assert.Equal(t, 403, code)
878 match := sipTest.signInRegexp.FindStringSubmatch(body)
880 t.Fatal("Did not find pattern in body: " +
881 signInRedirectPattern + "\nBody:\n" + body)
883 if match[1] != endpoint {
884 t.Fatal(`expected redirect to "` + endpoint +
885 `", but was "` + match[1] + `"`)
889func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
890 sipTest, err := NewSignInPageTest(false)
894 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
895 assert.Equal(t, 200, code)
897 match := sipTest.signInRegexp.FindStringSubmatch(body)
899 t.Fatal("Did not find pattern in body: " +
900 signInRedirectPattern + "\nBody:\n" + body)
903 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
907func TestSignInPageSkipProvider(t *testing.T) {
908 sipTest, err := NewSignInPageTest(true)
913 endpoint := "/some/random/endpoint"
915 code, body := sipTest.GetEndpoint(endpoint)
916 assert.Equal(t, 302, code)
918 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
920 t.Fatal("Did not find pattern in body: " +
921 signInSkipProvider + "\nBody:\n" + body)
925func TestSignInPageSkipProviderDirect(t *testing.T) {
926 sipTest, err := NewSignInPageTest(true)
931 endpoint := "/sign_in"
933 code, body := sipTest.GetEndpoint(endpoint)
934 assert.Equal(t, 302, code)
936 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
938 t.Fatal("Did not find pattern in body: " +
939 signInSkipProvider + "\nBody:\n" + body)
943type ProcessCookieTest struct {
944 opts *options.Options
946 rw *httptest.ResponseRecorder
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)
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)
972 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
973 return pcTest.validateUser
978 pcTest.proxy.provider = &TestProvider{
979 ProviderData: &providers.ProviderData{},
980 ValidToken: opts.providerValidateCookieResponse,
982 pcTest.proxy.provider.(*TestProvider).SetAllowedGroups(pcTest.opts.AllowedGroups)
984 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
985 // access_token validation.
986 pcTest.proxy.CookieRefresh = time.Duration(0)
987 pcTest.rw = httptest.NewRecorder()
988 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
989 pcTest.validateUser = true
993func NewProcessCookieTestWithDefaults() (*ProcessCookieTest, error) {
994 return NewProcessCookieTest(ProcessCookieTestOpts{
995 providerValidateCookieResponse: true,
999func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1000 return NewProcessCookieTest(ProcessCookieTestOpts{
1001 providerValidateCookieResponse: true,
1005func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
1006 err := p.proxy.SaveSession(p.rw, p.req, s)
1010 for _, cookie := range p.rw.Result().Cookies() {
1011 p.req.AddCookie(cookie)
1016func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
1017 return p.proxy.LoadCookiedSession(p.req)
1020func TestLoadCookiedSession(t *testing.T) {
1021 pcTest, err := NewProcessCookieTestWithDefaults()
1026 created := time.Now()
1027 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
1028 err = pcTest.SaveSession(startSession)
1029 assert.NoError(t, err)
1031 session, err := pcTest.LoadCookiedSession()
1035 assert.Equal(t, startSession.Email, session.Email)
1036 assert.Equal(t, "", session.User)
1037 assert.Equal(t, startSession.AccessToken, session.AccessToken)
1040func TestProcessCookieNoCookieError(t *testing.T) {
1041 pcTest, err := NewProcessCookieTestWithDefaults()
1046 session, err := pcTest.LoadCookiedSession()
1047 assert.Error(t, err, "cookie \"_oauth2_proxy\" not present")
1049 t.Errorf("expected nil session. got %#v", session)
1053func TestProcessCookieRefreshNotSet(t *testing.T) {
1054 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1055 opts.Cookie.Expire = time.Duration(23) * time.Hour
1061 reference := time.Now().Add(time.Duration(-2) * time.Hour)
1063 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1064 err = pcTest.SaveSession(startSession)
1065 assert.NoError(t, err)
1067 session, err := pcTest.LoadCookiedSession()
1068 assert.Equal(t, nil, err)
1069 if session.Age() < time.Duration(-2)*time.Hour {
1070 t.Errorf("cookie too young %v", session.Age())
1072 assert.Equal(t, startSession.Email, session.Email)
1075func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1076 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1077 opts.Cookie.Expire = time.Duration(24) * time.Hour
1083 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1084 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1085 err = pcTest.SaveSession(startSession)
1086 assert.NoError(t, err)
1088 session, err := pcTest.LoadCookiedSession()
1089 assert.NotEqual(t, nil, err)
1091 t.Errorf("expected nil session %#v", session)
1095func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1096 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1097 opts.Cookie.Expire = time.Duration(24) * time.Hour
1103 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1104 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1105 err = pcTest.SaveSession(startSession)
1106 assert.NoError(t, err)
1108 pcTest.proxy.CookieRefresh = time.Hour
1109 session, err := pcTest.LoadCookiedSession()
1110 assert.NotEqual(t, nil, err)
1112 t.Errorf("expected nil session %#v", session)
1116func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
1117 pcTest, err := NewProcessCookieTestWithDefaults()
1121 pcTest.req, _ = http.NewRequest("GET",
1122 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1126func TestUserInfoEndpointAccepted(t *testing.T) {
1127 testCases := []struct {
1129 session *sessions.SessionState
1130 expectedResponse string
1133 name: "Full session",
1134 session: &sessions.SessionState{
1136 Email: "john.doe@example.com",
1137 Groups: []string{"example", "groups"},
1138 AccessToken: "my_access_token",
1140 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n",
1143 name: "Minimal session",
1144 session: &sessions.SessionState{
1146 Email: "john.doe@example.com",
1147 Groups: []string{"example", "groups"},
1149 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n",
1153 session: &sessions.SessionState{
1155 Email: "john.doe@example.com",
1156 AccessToken: "my_access_token",
1158 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\"}\n",
1161 name: "With Preferred Username",
1162 session: &sessions.SessionState{
1164 PreferredUsername: "john",
1165 Email: "john.doe@example.com",
1166 Groups: []string{"example", "groups"},
1167 AccessToken: "my_access_token",
1169 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"],\"preferredUsername\":\"john\"}\n",
1173 for _, tc := range testCases {
1174 t.Run(tc.name, func(t *testing.T) {
1175 test, err := NewUserInfoEndpointTest()
1179 err = test.SaveSession(tc.session)
1180 assert.NoError(t, err)
1182 test.proxy.ServeHTTP(test.rw, test.req)
1183 assert.Equal(t, http.StatusOK, test.rw.Code)
1184 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1185 assert.Equal(t, tc.expectedResponse, string(bodyBytes))
1190func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1191 test, err := NewUserInfoEndpointTest()
1196 test.proxy.ServeHTTP(test.rw, test.req)
1197 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1200func NewAuthOnlyEndpointTest(querystring string, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1201 pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1205 pcTest.req, _ = http.NewRequest(
1207 fmt.Sprintf("%s/auth%s", pcTest.opts.ProxyPrefix, querystring),
1212func TestAuthOnlyEndpointAccepted(t *testing.T) {
1213 test, err := NewAuthOnlyEndpointTest("")
1218 created := time.Now()
1219 startSession := &sessions.SessionState{
1220 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1221 err = test.SaveSession(startSession)
1222 assert.NoError(t, err)
1224 test.proxy.ServeHTTP(test.rw, test.req)
1225 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1226 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1227 assert.Equal(t, "", string(bodyBytes))
1230func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1231 test, err := NewAuthOnlyEndpointTest("")
1236 test.proxy.ServeHTTP(test.rw, test.req)
1237 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1238 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1239 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1242func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1243 test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
1244 opts.Cookie.Expire = time.Duration(24) * time.Hour
1250 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1251 startSession := &sessions.SessionState{
1252 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1253 err = test.SaveSession(startSession)
1254 assert.NoError(t, err)
1256 test.proxy.ServeHTTP(test.rw, test.req)
1257 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1258 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1259 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1262func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1263 test, err := NewAuthOnlyEndpointTest("")
1268 created := time.Now()
1269 startSession := &sessions.SessionState{
1270 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1271 err = test.SaveSession(startSession)
1272 assert.NoError(t, err)
1273 test.validateUser = false
1275 test.proxy.ServeHTTP(test.rw, test.req)
1276 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1277 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1278 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1281func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1282 var pcTest ProcessCookieTest
1284 pcTest.opts = baseTestOptions()
1285 pcTest.opts.InjectResponseHeaders = []options.Header{
1287 Name: "X-Auth-Request-User",
1288 Values: []options.HeaderValue{
1290 ClaimSource: &options.ClaimSource{
1297 Name: "X-Auth-Request-Email",
1298 Values: []options.HeaderValue{
1300 ClaimSource: &options.ClaimSource{
1307 Name: "X-Auth-Request-Groups",
1308 Values: []options.HeaderValue{
1310 ClaimSource: &options.ClaimSource{
1317 Name: "X-Forwarded-Preferred-Username",
1318 Values: []options.HeaderValue{
1320 ClaimSource: &options.ClaimSource{
1321 Claim: "preferred_username",
1327 pcTest.opts.AllowedGroups = []string{"oauth_groups"}
1328 err := validation.Validate(pcTest.opts)
1329 assert.NoError(t, err)
1331 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1332 return pcTest.validateUser
1337 pcTest.proxy.provider = &TestProvider{
1338 ProviderData: &providers.ProviderData{},
1342 pcTest.validateUser = true
1344 pcTest.rw = httptest.NewRecorder()
1345 pcTest.req, _ = http.NewRequest("GET",
1346 pcTest.opts.ProxyPrefix+"/auth", nil)
1348 created := time.Now()
1349 startSession := &sessions.SessionState{
1350 User: "oauth_user", Groups: []string{"oauth_groups"}, Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1351 err = pcTest.SaveSession(startSession)
1352 assert.NoError(t, err)
1354 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1355 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1356 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1357 assert.Equal(t, startSession.Groups, pcTest.rw.Header().Values("X-Auth-Request-Groups"))
1358 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1361func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1362 var pcTest ProcessCookieTest
1364 pcTest.opts = baseTestOptions()
1365 pcTest.opts.InjectResponseHeaders = []options.Header{
1367 Name: "X-Auth-Request-User",
1368 Values: []options.HeaderValue{
1370 ClaimSource: &options.ClaimSource{
1377 Name: "X-Auth-Request-Email",
1378 Values: []options.HeaderValue{
1380 ClaimSource: &options.ClaimSource{
1387 Name: "X-Auth-Request-Groups",
1388 Values: []options.HeaderValue{
1390 ClaimSource: &options.ClaimSource{
1397 Name: "X-Forwarded-Preferred-Username",
1398 Values: []options.HeaderValue{
1400 ClaimSource: &options.ClaimSource{
1401 Claim: "preferred_username",
1407 Name: "Authorization",
1408 Values: []options.HeaderValue{
1410 ClaimSource: &options.ClaimSource{
1412 BasicAuthPassword: &options.SecretSource{
1413 Value: []byte("This is a secure password"),
1421 err := validation.Validate(pcTest.opts)
1422 assert.NoError(t, err)
1424 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1425 return pcTest.validateUser
1430 pcTest.proxy.provider = &TestProvider{
1431 ProviderData: &providers.ProviderData{},
1435 pcTest.validateUser = true
1437 pcTest.rw = httptest.NewRecorder()
1438 pcTest.req, _ = http.NewRequest("GET",
1439 pcTest.opts.ProxyPrefix+"/auth", nil)
1441 created := time.Now()
1442 startSession := &sessions.SessionState{
1443 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1444 err = pcTest.SaveSession(startSession)
1445 assert.NoError(t, err)
1447 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1448 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1449 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1450 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1451 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:This is a secure password"))
1452 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1455func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1456 var pcTest ProcessCookieTest
1458 pcTest.opts = baseTestOptions()
1459 pcTest.opts.InjectResponseHeaders = []options.Header{
1461 Name: "X-Auth-Request-User",
1462 Values: []options.HeaderValue{
1464 ClaimSource: &options.ClaimSource{
1471 Name: "X-Auth-Request-Email",
1472 Values: []options.HeaderValue{
1474 ClaimSource: &options.ClaimSource{
1481 Name: "X-Auth-Request-Groups",
1482 Values: []options.HeaderValue{
1484 ClaimSource: &options.ClaimSource{
1491 Name: "X-Forwarded-Preferred-Username",
1492 Values: []options.HeaderValue{
1494 ClaimSource: &options.ClaimSource{
1495 Claim: "preferred_username",
1501 err := validation.Validate(pcTest.opts)
1502 assert.NoError(t, err)
1504 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1505 return pcTest.validateUser
1510 pcTest.proxy.provider = &TestProvider{
1511 ProviderData: &providers.ProviderData{},
1515 pcTest.validateUser = true
1517 pcTest.rw = httptest.NewRecorder()
1518 pcTest.req, _ = http.NewRequest("GET",
1519 pcTest.opts.ProxyPrefix+"/auth", nil)
1521 created := time.Now()
1522 startSession := &sessions.SessionState{
1523 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1524 err = pcTest.SaveSession(startSession)
1525 assert.NoError(t, err)
1527 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1528 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1529 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1530 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1531 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1534func TestAuthSkippedForPreflightRequests(t *testing.T) {
1535 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1537 _, err := w.Write([]byte("response"))
1542 t.Cleanup(upstreamServer.Close)
1544 opts := baseTestOptions()
1545 opts.UpstreamServers = options.Upstreams{
1547 ID: upstreamServer.URL,
1549 URI: upstreamServer.URL,
1552 opts.SkipAuthPreflight = true
1553 err := validation.Validate(opts)
1554 assert.NoError(t, err)
1556 upstreamURL, _ := url.Parse(upstreamServer.URL)
1557 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1559 proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
1563 rw := httptest.NewRecorder()
1564 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1565 proxy.ServeHTTP(rw, req)
1567 assert.Equal(t, 200, rw.Code)
1568 assert.Equal(t, "response", rw.Body.String())
1571type SignatureAuthenticator struct {
1572 auth hmacauth.HmacAuth
1575func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1576 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1580 case hmacauth.ResultNoSignature:
1581 msg = "no signature received"
1582 case hmacauth.ResultMatch:
1583 msg = "signatures match"
1584 case hmacauth.ResultMismatch:
1586 "signatures do not match:\n received: %s\n computed: %s",
1590 panic("unknown result value: " + result.String())
1593 _, err := w.Write([]byte(msg))
1599type SignatureTest struct {
1600 opts *options.Options
1601 upstream *httptest.Server
1603 provider *httptest.Server
1605 rw *httptest.ResponseRecorder
1606 authenticator *SignatureAuthenticator
1609func NewSignatureTest() (*SignatureTest, error) {
1610 opts := baseTestOptions()
1611 opts.EmailDomains = []string{"acm.org"}
1613 authenticator := &SignatureAuthenticator{}
1614 upstreamServer := httptest.NewServer(
1615 http.HandlerFunc(authenticator.Authenticate))
1616 upstreamURL, err := url.Parse(upstreamServer.URL)
1620 opts.UpstreamServers = options.Upstreams{
1622 ID: upstreamServer.URL,
1624 URI: upstreamServer.URL,
1628 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1629 _, err := w.Write([]byte(`{"access_token": "my_auth_token"}`))
1634 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1635 providerURL, err := url.Parse(provider.URL)
1639 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1641 return &SignatureTest{
1647 httptest.NewRecorder(),
1652func (st *SignatureTest) Close() {
1657// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1658// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1659// https://github.com/18F/hmacauth/pull/4
1660type fakeNetConn struct {
1664func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1665 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1666 copy(p, fnc.reqBody)
1668 return bodyLen, io.EOF
1673func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) error {
1674 err := validation.Validate(st.opts)
1678 proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
1683 var bodyBuf io.ReadCloser
1685 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1687 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1688 req.Header = st.header
1690 state := &sessions.SessionState{
1691 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1692 err = proxy.SaveSession(st.rw, req, state)
1696 for _, c := range st.rw.Result().Cookies() {
1699 // This is used by the upstream to validate the signature.
1700 st.authenticator.auth = hmacauth.NewHmacAuth(
1701 crypto.SHA1, []byte(key), upstream.SignatureHeader, upstream.SignatureHeaders)
1702 proxy.ServeHTTP(st.rw, req)
1707func TestRequestSignature(t *testing.T) {
1708 testCases := map[string]struct {
1714 "No request signature": {
1718 resp: "no signature received",
1723 key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1724 resp: "signatures match",
1728 body: `{ "hello": "world!" }`,
1729 key: "d90df39e2d19282840252612dd7c81421a372f61",
1730 resp: "signatures match",
1733 for name, tc := range testCases {
1734 t.Run(name, func(t *testing.T) {
1735 st, err := NewSignatureTest()
1741 st.opts.SignatureKey = fmt.Sprintf("sha1:%s", tc.key)
1743 err = st.MakeRequestWithExpectedKey(tc.method, tc.body, tc.key)
1744 assert.NoError(t, err)
1745 assert.Equal(t, 200, st.rw.Code)
1746 assert.Equal(t, tc.resp, st.rw.Body.String())
1751func TestGetRedirect(t *testing.T) {
1752 opts := baseTestOptions()
1753 opts.WhitelistDomains = append(opts.WhitelistDomains, ".example.com")
1754 opts.WhitelistDomains = append(opts.WhitelistDomains, ".example.com:8443")
1755 err := validation.Validate(opts)
1756 assert.NoError(t, err)
1757 require.NotEmpty(t, opts.ProxyPrefix)
1758 proxy, err := NewOAuthProxy(opts, func(s string) bool { return false })
1766 headers map[string]string
1768 expectedRedirect string
1771 name: "request outside of ProxyPrefix redirects to original URL",
1774 reverseProxy: false,
1775 expectedRedirect: "/foo/bar",
1778 name: "request with query preserves query",
1781 reverseProxy: false,
1782 expectedRedirect: "/foo?bar",
1785 name: "request under ProxyPrefix redirects to root",
1786 url: proxy.ProxyPrefix + "/foo/bar",
1788 reverseProxy: false,
1789 expectedRedirect: "/",
1792 name: "proxied request outside of ProxyPrefix redirects to proxied URL",
1793 url: "https://oauth.example.com/foo/bar",
1794 headers: map[string]string{
1795 "X-Forwarded-Proto": "https",
1796 "X-Forwarded-Host": "a-service.example.com",
1797 "X-Forwarded-Uri": "/foo/bar",
1800 expectedRedirect: "https://a-service.example.com/foo/bar",
1803 name: "non-proxied request with spoofed proxy headers wouldn't redirect",
1804 url: "https://oauth.example.com/foo?bar",
1805 headers: map[string]string{
1806 "X-Forwarded-Proto": "https",
1807 "X-Forwarded-Host": "a-service.example.com",
1808 "X-Forwarded-Uri": "/foo/bar",
1810 reverseProxy: false,
1811 expectedRedirect: "/foo?bar",
1814 name: "proxied request under ProxyPrefix redirects to root",
1815 url: "https://oauth.example.com" + proxy.ProxyPrefix + "/foo/bar",
1816 headers: map[string]string{
1817 "X-Forwarded-Proto": "https",
1818 "X-Forwarded-Host": "a-service.example.com",
1819 "X-Forwarded-Uri": proxy.ProxyPrefix + "/foo/bar",
1822 expectedRedirect: "https://a-service.example.com/",
1825 name: "proxied request with port under ProxyPrefix redirects to root",
1826 url: "https://oauth.example.com" + proxy.ProxyPrefix + "/foo/bar",
1827 headers: map[string]string{
1828 "X-Forwarded-Proto": "https",
1829 "X-Forwarded-Host": "a-service.example.com:8443",
1830 "X-Forwarded-Uri": proxy.ProxyPrefix + "/foo/bar",
1833 expectedRedirect: "https://a-service.example.com:8443/",
1836 name: "proxied request with missing uri header would still redirect to desired redirect",
1837 url: "https://oauth.example.com/foo?bar",
1838 headers: map[string]string{
1839 "X-Forwarded-Proto": "https",
1840 "X-Forwarded-Host": "a-service.example.com",
1843 expectedRedirect: "https://a-service.example.com/foo?bar",
1846 name: "request with headers proxy not being set (and reverse proxy enabled) would still redirect to desired redirect",
1847 url: "https://oauth.example.com/foo?bar",
1850 expectedRedirect: "/foo?bar",
1853 name: "proxied request with X-Auth-Request-Redirect being set outside of ProxyPrefix redirects to proxied URL",
1854 url: "https://oauth.example.com/foo/bar",
1855 headers: map[string]string{
1856 "X-Auth-Request-Redirect": "https://a-service.example.com/foo/bar",
1857 "X-Forwarded-Proto": "",
1858 "X-Forwarded-Host": "",
1859 "X-Forwarded-Uri": "",
1862 expectedRedirect: "https://a-service.example.com/foo/bar",
1865 name: "proxied request with rd query string redirects to proxied URL",
1866 url: "https://oauth.example.com/foo/bar?rd=https%3A%2F%2Fa%2Dservice%2Eexample%2Ecom%2Ffoo%2Fbar",
1868 reverseProxy: false,
1869 expectedRedirect: "https://a-service.example.com/foo/bar",
1872 name: "proxied request with rd query string and all headers set (and reverse proxy not enabled) redirects to proxied URL on rd query string",
1873 url: "https://oauth.example.com/foo/bar?rd=https%3A%2F%2Fa%2Dservice%2Eexample%2Ecom%2Ffoo%2Fjazz",
1874 headers: map[string]string{
1875 "X-Auth-Request-Redirect": "https://a-service.example.com/foo/baz",
1876 "X-Forwarded-Proto": "http",
1877 "X-Forwarded-Host": "another-service.example.com",
1878 "X-Forwarded-Uri": "/seasons/greetings",
1880 reverseProxy: false,
1881 expectedRedirect: "https://a-service.example.com/foo/jazz",
1884 name: "proxied request with rd query string and some headers set redirects to proxied URL on rd query string",
1885 url: "https://oauth.example.com/foo/bar?rd=https%3A%2F%2Fa%2Dservice%2Eexample%2Ecom%2Ffoo%2Fbaz",
1886 headers: map[string]string{
1887 "X-Auth-Request-Redirect": "",
1888 "X-Forwarded-Proto": "https",
1889 "X-Forwarded-Host": "another-service.example.com",
1890 "X-Forwarded-Uri": "/seasons/greetings",
1893 expectedRedirect: "https://a-service.example.com/foo/baz",
1896 for _, tt := range tests {
1897 t.Run(tt.name, func(t *testing.T) {
1898 req, _ := http.NewRequest("GET", tt.url, nil)
1899 for header, value := range tt.headers {
1901 req.Header.Add(header, value)
1904 proxy.ReverseProxy = tt.reverseProxy
1905 redirect, err := proxy.GetRedirect(req)
1907 assert.NoError(t, err)
1908 assert.Equal(t, tt.expectedRedirect, redirect)
1913type ajaxRequestTest struct {
1914 opts *options.Options
1918func newAjaxRequestTest() (*ajaxRequestTest, error) {
1919 test := &ajaxRequestTest{}
1920 test.opts = baseTestOptions()
1921 err := validation.Validate(test.opts)
1926 test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
1935func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1936 rw := httptest.NewRecorder()
1937 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1942 test.proxy.ServeHTTP(rw, req)
1943 return rw.Code, rw.Header(), nil
1946func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1947 test, err := newAjaxRequestTest()
1953 code, rh, err := test.getEndpoint(endpoint, header)
1954 assert.NoError(t, err)
1955 assert.Equal(t, http.StatusUnauthorized, code)
1956 mime := rh.Get("Content-Type")
1957 assert.Equal(t, applicationJSON, mime)
1959func TestAjaxUnauthorizedRequest1(t *testing.T) {
1960 header := make(http.Header)
1961 header.Add("accept", applicationJSON)
1963 testAjaxUnauthorizedRequest(t, header)
1966func TestAjaxUnauthorizedRequest2(t *testing.T) {
1967 header := make(http.Header)
1968 header.Add("Accept", applicationJSON)
1970 testAjaxUnauthorizedRequest(t, header)
1973func TestAjaxForbiddendRequest(t *testing.T) {
1974 test, err := newAjaxRequestTest()
1979 header := make(http.Header)
1980 code, rh, err := test.getEndpoint(endpoint, header)
1981 assert.NoError(t, err)
1982 assert.Equal(t, http.StatusForbidden, code)
1983 mime := rh.Get("Content-Type")
1984 assert.NotEqual(t, applicationJSON, mime)
1987func TestClearSplitCookie(t *testing.T) {
1988 opts := baseTestOptions()
1989 opts.Cookie.Secret = base64CookieSecret
1990 opts.Cookie.Name = "oauth2"
1991 opts.Cookie.Domains = []string{"abc"}
1992 err := validation.Validate(opts)
1993 assert.NoError(t, err)
1995 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
2000 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
2001 var rw = httptest.NewRecorder()
2002 req := httptest.NewRequest("get", "/", nil)
2004 req.AddCookie(&http.Cookie{
2008 req.AddCookie(&http.Cookie{
2012 req.AddCookie(&http.Cookie{
2017 err = p.ClearSessionCookie(rw, req)
2018 assert.NoError(t, err)
2019 header := rw.Header()
2021 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
2024func TestClearSingleCookie(t *testing.T) {
2025 opts := baseTestOptions()
2026 opts.Cookie.Name = "oauth2"
2027 opts.Cookie.Domains = []string{"abc"}
2028 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
2033 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
2034 var rw = httptest.NewRecorder()
2035 req := httptest.NewRequest("get", "/", nil)
2037 req.AddCookie(&http.Cookie{
2041 req.AddCookie(&http.Cookie{
2046 err = p.ClearSessionCookie(rw, req)
2047 assert.NoError(t, err)
2048 header := rw.Header()
2050 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
2053type NoOpKeySet struct {
2056func (NoOpKeySet) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
2057 splitStrings := strings.Split(jwt, ".")
2058 payloadString := splitStrings[1]
2059 return base64.RawURLEncoding.DecodeString(payloadString)
2062func TestGetJwtSession(t *testing.T) {
2065 "sub": "1234567890",
2066 "aud": "https://test.myapp.com",
2068 "email": "john@example.com",
2069 "iss": "https://issuer.example.com",
2074 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
2075 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
2076 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
2077 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
2078 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
2079 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
2081 keyset := NoOpKeySet{}
2082 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
2083 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
2085 test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
2086 opts.InjectRequestHeaders = []options.Header{
2088 Name: "Authorization",
2089 Values: []options.HeaderValue{
2091 ClaimSource: &options.ClaimSource{
2099 Name: "X-Forwarded-User",
2100 Values: []options.HeaderValue{
2102 ClaimSource: &options.ClaimSource{
2109 Name: "X-Forwarded-Email",
2110 Values: []options.HeaderValue{
2112 ClaimSource: &options.ClaimSource{
2120 opts.InjectResponseHeaders = []options.Header{
2122 Name: "Authorization",
2123 Values: []options.HeaderValue{
2125 ClaimSource: &options.ClaimSource{
2133 Name: "X-Auth-Request-User",
2134 Values: []options.HeaderValue{
2136 ClaimSource: &options.ClaimSource{
2143 Name: "X-Auth-Request-Email",
2144 Values: []options.HeaderValue{
2146 ClaimSource: &options.ClaimSource{
2153 opts.SkipJwtBearerTokens = true
2154 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
2159 tp, _ := test.proxy.provider.(*TestProvider)
2160 tp.GroupValidator = func(s string) bool {
2164 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
2165 test.req.Header = map[string][]string{
2166 "Authorization": {authHeader},
2169 test.proxy.ServeHTTP(test.rw, test.req)
2170 if test.rw.Code >= 400 {
2171 t.Fatalf("expected 3xx got %d", test.rw.Code)
2174 // Check PassAuthorization, should overwrite Basic header
2175 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
2176 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "1234567890")
2177 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
2179 // SetAuthorization and SetXAuthRequest
2180 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
2181 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "1234567890")
2182 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
2185func Test_prepareNoCache(t *testing.T) {
2186 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2189 mux := http.NewServeMux()
2190 mux.Handle("/", handler)
2192 rec := httptest.NewRecorder()
2193 req := httptest.NewRequest(http.MethodGet, "/", nil)
2194 mux.ServeHTTP(rec, req)
2196 for k, v := range noCacheHeaders {
2197 assert.Equal(t, rec.Header().Get(k), v)
2201func Test_noCacheHeaders(t *testing.T) {
2202 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2203 _, err := w.Write([]byte("upstream"))
2208 t.Cleanup(upstreamServer.Close)
2210 opts := baseTestOptions()
2211 opts.UpstreamServers = options.Upstreams{
2213 ID: upstreamServer.URL,
2215 URI: upstreamServer.URL,
2218 opts.SkipAuthRegex = []string{".*"}
2219 err := validation.Validate(opts)
2220 assert.NoError(t, err)
2221 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2226 t.Run("not exist in response from upstream", func(t *testing.T) {
2227 rec := httptest.NewRecorder()
2228 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
2229 proxy.ServeHTTP(rec, req)
2231 assert.Equal(t, http.StatusOK, rec.Code)
2232 assert.Equal(t, "upstream", rec.Body.String())
2234 // checking noCacheHeaders does not exists in response headers from upstream
2235 for k := range noCacheHeaders {
2236 assert.Equal(t, "", rec.Header().Get(k))
2240 t.Run("has no-cache", func(t *testing.T) {
2246 path: "/oauth2/sign_in",
2250 path: "/oauth2/sign_out",
2254 path: "/oauth2/start",
2258 path: "/oauth2/callback",
2262 path: "/oauth2/auth",
2266 path: "/oauth2/userinfo",
2275 for _, tt := range tests {
2276 t.Run(tt.path, func(t *testing.T) {
2277 rec := httptest.NewRecorder()
2278 req := httptest.NewRequest(http.MethodGet, tt.path, nil)
2279 proxy.ServeHTTP(rec, req)
2280 cacheControl := rec.Result().Header.Get("Cache-Control")
2281 if tt.hasNoCache != (strings.Contains(cacheControl, "no-cache")) {
2282 t.Errorf(`unexpected "Cache-Control" header: %s`, cacheControl)
2290func baseTestOptions() *options.Options {
2291 opts := options.NewOptions()
2292 opts.Cookie.Secret = rawCookieSecret
2293 opts.ClientID = clientID
2294 opts.ClientSecret = clientSecret
2295 opts.EmailDomains = []string{"*"}
2297 // Default injected headers for legacy configuration
2298 opts.InjectRequestHeaders = []options.Header{
2300 Name: "Authorization",
2301 Values: []options.HeaderValue{
2303 ClaimSource: &options.ClaimSource{
2305 BasicAuthPassword: &options.SecretSource{
2306 Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
2313 Name: "X-Forwarded-User",
2314 Values: []options.HeaderValue{
2316 ClaimSource: &options.ClaimSource{
2323 Name: "X-Forwarded-Email",
2324 Values: []options.HeaderValue{
2326 ClaimSource: &options.ClaimSource{
2336func TestTrustedIPs(t *testing.T) {
2341 realClientIPHeader string
2345 // Check unconfigured behavior.
2349 reverseProxy: false,
2350 realClientIPHeader: "X-Real-IP", // Default value
2351 req: func() *http.Request {
2352 req, _ := http.NewRequest("GET", "/", nil)
2355 expectTrusted: false,
2357 // Check using req.RemoteAddr (Options.ReverseProxy == false).
2359 name: "WithRemoteAddr",
2360 trustedIPs: []string{"127.0.0.1"},
2361 reverseProxy: false,
2362 realClientIPHeader: "X-Real-IP", // Default value
2363 req: func() *http.Request {
2364 req, _ := http.NewRequest("GET", "/", nil)
2365 req.RemoteAddr = "127.0.0.1:43670"
2368 expectTrusted: true,
2370 // Check ignores req.RemoteAddr match when behind a reverse proxy / missing header.
2372 name: "IgnoresRemoteAddrInReverseProxyMode",
2373 trustedIPs: []string{"127.0.0.1"},
2375 realClientIPHeader: "X-Real-IP", // Default value
2376 req: func() *http.Request {
2377 req, _ := http.NewRequest("GET", "/", nil)
2378 req.RemoteAddr = "127.0.0.1:44324"
2381 expectTrusted: false,
2383 // Check successful trusting of localhost in IPv4.
2385 name: "TrustsLocalhostInReverseProxyMode",
2386 trustedIPs: []string{"127.0.0.0/8", "::1"},
2388 realClientIPHeader: "X-Forwarded-For",
2389 req: func() *http.Request {
2390 req, _ := http.NewRequest("GET", "/", nil)
2391 req.Header.Add("X-Forwarded-For", "127.0.0.1")
2394 expectTrusted: true,
2396 // Check successful trusting of localhost in IPv6.
2398 name: "TrustsIP6LocalostInReverseProxyMode",
2399 trustedIPs: []string{"127.0.0.0/8", "::1"},
2401 realClientIPHeader: "X-Forwarded-For",
2402 req: func() *http.Request {
2403 req, _ := http.NewRequest("GET", "/", nil)
2404 req.Header.Add("X-Forwarded-For", "::1")
2407 expectTrusted: true,
2409 // Check does not trust random IPv4 address.
2411 name: "DoesNotTrustRandomIP4Address",
2412 trustedIPs: []string{"127.0.0.0/8", "::1"},
2414 realClientIPHeader: "X-Forwarded-For",
2415 req: func() *http.Request {
2416 req, _ := http.NewRequest("GET", "/", nil)
2417 req.Header.Add("X-Forwarded-For", "12.34.56.78")
2420 expectTrusted: false,
2422 // Check does not trust random IPv6 address.
2424 name: "DoesNotTrustRandomIP6Address",
2425 trustedIPs: []string{"127.0.0.0/8", "::1"},
2427 realClientIPHeader: "X-Forwarded-For",
2428 req: func() *http.Request {
2429 req, _ := http.NewRequest("GET", "/", nil)
2430 req.Header.Add("X-Forwarded-For", "::2")
2433 expectTrusted: false,
2435 // Check respects correct header.
2437 name: "RespectsCorrectHeaderInReverseProxyMode",
2438 trustedIPs: []string{"127.0.0.0/8", "::1"},
2440 realClientIPHeader: "X-Forwarded-For",
2441 req: func() *http.Request {
2442 req, _ := http.NewRequest("GET", "/", nil)
2443 req.Header.Add("X-Real-IP", "::1")
2446 expectTrusted: false,
2448 // Check doesn't trust if garbage is provided.
2450 name: "DoesNotTrustGarbageInReverseProxyMode",
2451 trustedIPs: []string{"127.0.0.0/8", "::1"},
2453 realClientIPHeader: "X-Forwarded-For",
2454 req: func() *http.Request {
2455 req, _ := http.NewRequest("GET", "/", nil)
2456 req.Header.Add("X-Forwarded-For", "adsfljk29242as!!")
2459 expectTrusted: false,
2461 // Check doesn't trust if garbage is provided (no reverse-proxy).
2463 name: "DoesNotTrustGarbage",
2464 trustedIPs: []string{"127.0.0.0/8", "::1"},
2465 reverseProxy: false,
2466 realClientIPHeader: "X-Real-IP",
2467 req: func() *http.Request {
2468 req, _ := http.NewRequest("GET", "/", nil)
2469 req.RemoteAddr = "adsfljk29242as!!"
2472 expectTrusted: false,
2476 for _, tt := range tests {
2477 t.Run(tt.name, func(t *testing.T) {
2478 opts := baseTestOptions()
2479 opts.UpstreamServers = options.Upstreams{
2486 opts.TrustedIPs = tt.trustedIPs
2487 opts.ReverseProxy = tt.reverseProxy
2488 opts.RealClientIPHeader = tt.realClientIPHeader
2489 err := validation.Validate(opts)
2490 assert.NoError(t, err)
2492 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
2493 assert.NoError(t, err)
2494 rw := httptest.NewRecorder()
2496 proxy.ServeHTTP(rw, tt.req)
2497 if tt.expectTrusted {
2498 assert.Equal(t, 200, rw.Code)
2500 assert.Equal(t, 403, rw.Code)
2506func Test_buildRoutesAllowlist(t *testing.T) {
2507 type expectedAllowedRoute struct {
2512 testCases := []struct {
2514 skipAuthRegex []string
2515 skipAuthRoutes []string
2516 expectedRoutes []expectedAllowedRoute
2520 name: "No skip auth configured",
2521 skipAuthRegex: []string{},
2522 skipAuthRoutes: []string{},
2523 expectedRoutes: []expectedAllowedRoute{},
2527 name: "Only skipAuthRegex configured",
2528 skipAuthRegex: []string{
2530 "^/baz/[0-9]+/thing",
2532 skipAuthRoutes: []string{},
2533 expectedRoutes: []expectedAllowedRoute{
2536 regexString: "^/foo/bar",
2540 regexString: "^/baz/[0-9]+/thing",
2546 name: "Only skipAuthRoutes configured",
2547 skipAuthRegex: []string{},
2548 skipAuthRoutes: []string{
2550 "POST=^/baz/[0-9]+/thing",
2552 "WEIRD=^/methods/are/allowed",
2553 "PATCH=/second/equals?are=handled&just=fine",
2555 expectedRoutes: []expectedAllowedRoute{
2558 regexString: "^/foo/bar",
2562 regexString: "^/baz/[0-9]+/thing",
2566 regexString: "^/all/methods$",
2570 regexString: "^/methods/are/allowed",
2574 regexString: "/second/equals?are=handled&just=fine",
2580 name: "Both skipAuthRegexes and skipAuthRoutes configured",
2581 skipAuthRegex: []string{
2583 "^/baz/[0-9]+/thing/regex",
2585 skipAuthRoutes: []string{
2587 "POST=^/baz/[0-9]+/thing",
2590 expectedRoutes: []expectedAllowedRoute{
2593 regexString: "^/foo/bar/regex",
2597 regexString: "^/baz/[0-9]+/thing/regex",
2601 regexString: "^/foo/bar",
2605 regexString: "^/baz/[0-9]+/thing",
2609 regexString: "^/all/methods$",
2615 name: "Invalid skipAuthRegex entry",
2616 skipAuthRegex: []string{
2618 "^/baz/[0-9]+/thing",
2621 skipAuthRoutes: []string{},
2622 expectedRoutes: []expectedAllowedRoute{},
2626 name: "Invalid skipAuthRoutes entry",
2627 skipAuthRegex: []string{},
2628 skipAuthRoutes: []string{
2630 "POST=^/baz/[0-9]+/thing",
2634 expectedRoutes: []expectedAllowedRoute{},
2639 for _, tc := range testCases {
2640 t.Run(tc.name, func(t *testing.T) {
2641 opts := &options.Options{
2642 SkipAuthRegex: tc.skipAuthRegex,
2643 SkipAuthRoutes: tc.skipAuthRoutes,
2645 routes, err := buildRoutesAllowlist(opts)
2647 assert.Error(t, err)
2650 assert.NoError(t, err)
2652 for i, route := range routes {
2653 assert.Greater(t, len(tc.expectedRoutes), i)
2654 assert.Equal(t, route.method, tc.expectedRoutes[i].method)
2655 assert.Equal(t, route.pathRegex.String(), tc.expectedRoutes[i].regexString)
2661func TestAllowedRequest(t *testing.T) {
2662 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2664 _, err := w.Write([]byte("Allowed Request"))
2669 t.Cleanup(upstreamServer.Close)
2671 opts := baseTestOptions()
2672 opts.UpstreamServers = options.Upstreams{
2674 ID: upstreamServer.URL,
2676 URI: upstreamServer.URL,
2679 opts.SkipAuthRegex = []string{
2680 "^/skip/auth/regex$",
2682 opts.SkipAuthRoutes = []string{
2683 "GET=^/skip/auth/routes/get",
2685 err := validation.Validate(opts)
2686 assert.NoError(t, err)
2687 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2692 testCases := []struct {
2699 name: "Regex GET allowed",
2701 url: "/skip/auth/regex",
2705 name: "Regex POST allowed ",
2707 url: "/skip/auth/regex",
2711 name: "Regex denied",
2713 url: "/wrong/denied",
2717 name: "Route allowed",
2719 url: "/skip/auth/routes/get",
2723 name: "Route denied with wrong method",
2725 url: "/skip/auth/routes/get",
2729 name: "Route denied with wrong path",
2731 url: "/skip/auth/routes/wrong/path",
2736 for _, tc := range testCases {
2737 t.Run(tc.name, func(t *testing.T) {
2738 req, err := http.NewRequest(tc.method, tc.url, nil)
2739 assert.NoError(t, err)
2740 assert.Equal(t, tc.allowed, proxy.isAllowedRoute(req))
2742 rw := httptest.NewRecorder()
2743 proxy.ServeHTTP(rw, req)
2746 assert.Equal(t, 200, rw.Code)
2747 assert.Equal(t, "Allowed Request", rw.Body.String())
2749 assert.Equal(t, 403, rw.Code)
2755func TestProxyAllowedGroups(t *testing.T) {
2758 allowedGroups []string
2760 expectUnauthorized bool
2762 {"NoAllowedGroups", []string{}, []string{}, false},
2763 {"NoAllowedGroupsUserHasGroups", []string{}, []string{"a", "b"}, false},
2764 {"UserInAllowedGroup", []string{"a"}, []string{"a", "b"}, false},
2765 {"UserNotInAllowedGroup", []string{"a"}, []string{"c"}, true},
2768 for _, tt := range tests {
2769 t.Run(tt.name, func(t *testing.T) {
2770 emailAddress := "test"
2771 created := time.Now()
2773 session := &sessions.SessionState{
2775 Email: emailAddress,
2776 AccessToken: "oauth_token",
2777 CreatedAt: &created,
2780 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2783 t.Cleanup(upstreamServer.Close)
2785 test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
2786 opts.AllowedGroups = tt.allowedGroups
2787 opts.UpstreamServers = options.Upstreams{
2789 ID: upstreamServer.URL,
2791 URI: upstreamServer.URL,
2799 test.req, _ = http.NewRequest("GET", "/", nil)
2801 test.req.Header.Add("accept", applicationJSON)
2802 err = test.SaveSession(session)
2803 assert.NoError(t, err)
2804 test.proxy.ServeHTTP(test.rw, test.req)
2806 if tt.expectUnauthorized {
2807 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
2809 assert.Equal(t, http.StatusOK, test.rw.Code)
2815func TestAuthOnlyAllowedGroups(t *testing.T) {
2816 testCases := []struct {
2818 allowedGroups []string
2821 expectedStatusCode int
2824 name: "NoAllowedGroups",
2825 allowedGroups: []string{},
2828 expectedStatusCode: http.StatusAccepted,
2831 name: "NoAllowedGroupsUserHasGroups",
2832 allowedGroups: []string{},
2833 groups: []string{"a", "b"},
2835 expectedStatusCode: http.StatusAccepted,
2838 name: "UserInAllowedGroup",
2839 allowedGroups: []string{"a"},
2840 groups: []string{"a", "b"},
2842 expectedStatusCode: http.StatusAccepted,
2845 name: "UserNotInAllowedGroup",
2846 allowedGroups: []string{"a"},
2847 groups: []string{"c"},
2849 expectedStatusCode: http.StatusUnauthorized,
2852 name: "UserInQuerystringGroup",
2853 allowedGroups: []string{"a", "b"},
2854 groups: []string{"a", "c"},
2855 querystring: "?allowed_groups=a",
2856 expectedStatusCode: http.StatusAccepted,
2859 name: "UserInMultiParamQuerystringGroup",
2860 allowedGroups: []string{"a", "b"},
2861 groups: []string{"b"},
2862 querystring: "?allowed_groups=a&allowed_groups=b,d",
2863 expectedStatusCode: http.StatusAccepted,
2866 name: "UserInOnlyQuerystringGroup",
2867 allowedGroups: []string{},
2868 groups: []string{"a", "c"},
2869 querystring: "?allowed_groups=a,b",
2870 expectedStatusCode: http.StatusAccepted,
2873 name: "UserInDelimitedQuerystringGroup",
2874 allowedGroups: []string{"a", "b", "c"},
2875 groups: []string{"c"},
2876 querystring: "?allowed_groups=a,c",
2877 expectedStatusCode: http.StatusAccepted,
2880 name: "UserNotInQuerystringGroup",
2881 allowedGroups: []string{},
2882 groups: []string{"c"},
2883 querystring: "?allowed_groups=a,b",
2884 expectedStatusCode: http.StatusForbidden,
2887 name: "UserInConfigGroupNotInQuerystringGroup",
2888 allowedGroups: []string{"a", "b", "c"},
2889 groups: []string{"c"},
2890 querystring: "?allowed_groups=a,b",
2891 expectedStatusCode: http.StatusForbidden,
2894 name: "UserInQuerystringGroupNotInConfigGroup",
2895 allowedGroups: []string{"a", "b"},
2896 groups: []string{"c"},
2897 querystring: "?allowed_groups=b,c",
2898 expectedStatusCode: http.StatusUnauthorized,
2902 for _, tc := range testCases {
2903 t.Run(tc.name, func(t *testing.T) {
2904 emailAddress := "test"
2905 created := time.Now()
2907 session := &sessions.SessionState{
2909 Email: emailAddress,
2910 AccessToken: "oauth_token",
2911 CreatedAt: &created,
2914 test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {
2915 opts.AllowedGroups = tc.allowedGroups
2921 err = test.SaveSession(session)
2922 assert.NoError(t, err)
2924 test.proxy.ServeHTTP(test.rw, test.req)
2926 assert.Equal(t, tc.expectedStatusCode, test.rw.Code)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list