20 "github.com/coreos/go-oidc"
21 "github.com/mbland/hmacauth"
22 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
23 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
24 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
25 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
26 sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
27 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
28 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
29 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
30 "github.com/stretchr/testify/assert"
31 "github.com/stretchr/testify/require"
35 // The rawCookieSecret is 32 bytes and the base64CookieSecret is the base64
36 // encoded version of this.
37 rawCookieSecret = "secretthirtytwobytes+abcdefghijk"
38 base64CookieSecret = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpams"
39 clientID = "3984n253984d7348dm8234yf982t"
40 clientSecret = "gv3498mfc9t23y23974dm2394dm9"
44 logger.SetFlags(logger.Lshortfile)
47func TestRobotsTxt(t *testing.T) {
48 opts := baseTestOptions()
49 err := validation.Validate(opts)
50 assert.NoError(t, err)
52 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
56 rw := httptest.NewRecorder()
57 req, _ := http.NewRequest("GET", "/robots.txt", nil)
58 proxy.ServeHTTP(rw, req)
59 assert.Equal(t, 200, rw.Code)
60 assert.Equal(t, "User-agent: *\nDisallow: /", rw.Body.String())
63func TestIsValidRedirect(t *testing.T) {
64 opts := baseTestOptions()
65 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
66 opts.WhitelistDomains = []string{
74 err := validation.Validate(opts)
75 assert.NoError(t, err)
77 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
82 testCases := []struct {
89 ExpectedResult: false,
93 Redirect: "/redirect",
98 Redirect: "//redirect",
99 ExpectedResult: false,
103 Redirect: "http://foo.bar/redirect",
104 ExpectedResult: true,
108 Redirect: "https://foo.bar/redirect",
109 ExpectedResult: true,
112 Desc: "invalidHTTPSubdomain",
113 Redirect: "http://baz.foo.bar/redirect",
114 ExpectedResult: false,
117 Desc: "invalidHTTPSSubdomain",
118 Redirect: "https://baz.foo.bar/redirect",
119 ExpectedResult: false,
122 Desc: "validHTTPSubdomain",
123 Redirect: "http://baz.bar.foo/redirect",
124 ExpectedResult: true,
127 Desc: "validHTTPSSubdomain",
128 Redirect: "https://baz.bar.foo/redirect",
129 ExpectedResult: true,
132 Desc: "validHTTPDomain",
133 Redirect: "http://bar.foo/redirect",
134 ExpectedResult: true,
137 Desc: "invalidHTTP1",
138 Redirect: "http://foo.bar.evil.corp/redirect",
139 ExpectedResult: false,
142 Desc: "invalidHTTPS1",
143 Redirect: "https://foo.bar.evil.corp/redirect",
144 ExpectedResult: false,
147 Desc: "invalidHTTP2",
148 Redirect: "http://evil.corp/redirect?rd=foo.bar",
149 ExpectedResult: false,
152 Desc: "invalidHTTPS2",
153 Redirect: "https://evil.corp/redirect?rd=foo.bar",
154 ExpectedResult: false,
158 Redirect: "https://evil.corp:3838/redirect",
159 ExpectedResult: false,
162 Desc: "invalidEmptyPort",
163 Redirect: "http://foo.bar:3838/redirect",
164 ExpectedResult: false,
167 Desc: "invalidEmptyPortSubdomain",
168 Redirect: "http://baz.bar.foo:3838/redirect",
169 ExpectedResult: false,
172 Desc: "validSpecificPort",
173 Redirect: "http://port.bar:8080/redirect",
174 ExpectedResult: true,
177 Desc: "invalidSpecificPort",
178 Redirect: "http://port.bar:3838/redirect",
179 ExpectedResult: false,
182 Desc: "validSpecificPortSubdomain",
183 Redirect: "http://foo.sub.port.bar:8080/redirect",
184 ExpectedResult: true,
187 Desc: "invalidSpecificPortSubdomain",
188 Redirect: "http://foo.sub.port.bar:3838/redirect",
189 ExpectedResult: false,
192 Desc: "validAnyPort1",
193 Redirect: "http://anyport.bar:8080/redirect",
194 ExpectedResult: true,
197 Desc: "validAnyPort2",
198 Redirect: "http://anyport.bar:8081/redirect",
199 ExpectedResult: true,
202 Desc: "validAnyPortSubdomain1",
203 Redirect: "http://a.sub.anyport.bar:8080/redirect",
204 ExpectedResult: true,
207 Desc: "validAnyPortSubdomain2",
208 Redirect: "http://a.sub.anyport.bar:8081/redirect",
209 ExpectedResult: true,
212 Desc: "openRedirect1",
213 Redirect: "/\\evil.com",
214 ExpectedResult: false,
217 Desc: "openRedirectSpace1",
218 Redirect: "/ /evil.com",
219 ExpectedResult: false,
222 Desc: "openRedirectSpace2",
223 Redirect: "/ \\evil.com",
224 ExpectedResult: false,
227 Desc: "openRedirectTab1",
228 Redirect: "/\t/evil.com",
229 ExpectedResult: false,
232 Desc: "openRedirectTab2",
233 Redirect: "/\t\\evil.com",
234 ExpectedResult: false,
237 Desc: "openRedirectVerticalTab1",
238 Redirect: "/\v/evil.com",
239 ExpectedResult: false,
242 Desc: "openRedirectVerticalTab2",
243 Redirect: "/\v\\evil.com",
244 ExpectedResult: false,
247 Desc: "openRedirectNewLine1",
248 Redirect: "/\n/evil.com",
249 ExpectedResult: false,
252 Desc: "openRedirectNewLine2",
253 Redirect: "/\n\\evil.com",
254 ExpectedResult: false,
257 Desc: "openRedirectCarriageReturn1",
258 Redirect: "/\r/evil.com",
259 ExpectedResult: false,
262 Desc: "openRedirectCarriageReturn2",
263 Redirect: "/\r\\evil.com",
264 ExpectedResult: false,
267 Desc: "openRedirectTripleTab",
268 Redirect: "/\t\t/\t/evil.com",
269 ExpectedResult: false,
272 Desc: "openRedirectTripleTab2",
273 Redirect: "/\t\t\\\t/evil.com",
274 ExpectedResult: false,
277 Desc: "openRedirectQuadTab1",
278 Redirect: "/\t\t/\t\t\\evil.com",
279 ExpectedResult: false,
282 Desc: "openRedirectQuadTab2",
283 Redirect: "/\t\t\\\t\t/evil.com",
284 ExpectedResult: false,
287 Desc: "openRedirectPeriod1",
288 Redirect: "/./\\evil.com",
289 ExpectedResult: false,
292 Desc: "openRedirectPeriod2",
293 Redirect: "/./../../\\evil.com",
294 ExpectedResult: false,
297 Desc: "openRedirectDoubleTab",
298 Redirect: "/\t/\t\\evil.com",
299 ExpectedResult: false,
302 Desc: "openRedirectPartialSubdomain",
303 Redirect: "http://evilbar.foo",
304 ExpectedResult: false,
308 for _, tc := range testCases {
309 t.Run(tc.Desc, func(t *testing.T) {
310 result := proxy.IsValidRedirect(tc.Redirect)
312 if result != tc.ExpectedResult {
313 t.Errorf("expected %t got %t", tc.ExpectedResult, result)
319func TestOpenRedirects(t *testing.T) {
320 opts := baseTestOptions()
321 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
322 opts.WhitelistDomains = []string{
326 ".sub.port.bar:8080",
328 ".sub.anyport.bar:*",
329 "www.whitelisteddomain.tld",
331 err := validation.Validate(opts)
332 assert.NoError(t, err)
334 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
339 file, err := os.Open("./testdata/openredirects.txt")
343 defer func(t *testing.T) {
344 if err := file.Close(); err != nil {
349 scanner := bufio.NewScanner(file)
352 t.Run(rd, func(t *testing.T) {
353 rdUnescaped, err := url.QueryUnescape(rd)
357 if proxy.IsValidRedirect(rdUnescaped) {
358 t.Errorf("Expected %q to not be valid (unescaped: %q)", rd, rdUnescaped)
363 if err := scanner.Err(); err != nil {
368type TestProvider struct {
369 *providers.ProviderData
372 GroupValidator func(string) bool
375var _ providers.Provider = (*TestProvider)(nil)
377func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
378 return &TestProvider{
379 ProviderData: &providers.ProviderData{
380 ProviderName: "Test Provider",
383 Host: providerURL.Host,
384 Path: "/oauth/authorize",
388 Host: providerURL.Host,
389 Path: "/oauth/token",
391 ProfileURL: &url.URL{
393 Host: providerURL.Host,
394 Path: "/api/v1/profile",
396 Scope: "profile.email",
398 EmailAddress: emailAddress,
399 GroupValidator: func(s string) bool {
405func (tp *TestProvider) GetEmailAddress(_ context.Context, _ *sessions.SessionState) (string, error) {
406 return tp.EmailAddress, nil
409func (tp *TestProvider) ValidateSession(_ context.Context, _ *sessions.SessionState) bool {
413func Test_redeemCode(t *testing.T) {
414 opts := baseTestOptions()
415 err := validation.Validate(opts)
416 assert.NoError(t, err)
418 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
423 req := httptest.NewRequest(http.MethodGet, "/", nil)
424 _, err = proxy.redeemCode(req)
425 assert.Equal(t, providers.ErrMissingCode, err)
428func Test_enrichSession(t *testing.T) {
430 sessionUser = "Mr Session"
431 sessionEmail = "session@example.com"
432 providerEmail = "provider@example.com"
435 testCases := map[string]struct {
436 session *sessions.SessionState
440 "Session already has enrichable fields": {
441 session: &sessions.SessionState{
445 expectedUser: sessionUser,
446 expectedEmail: sessionEmail,
448 "Session is missing Email and GetEmailAddress is implemented": {
449 session: &sessions.SessionState{
452 expectedUser: sessionUser,
453 expectedEmail: providerEmail,
455 "Session is missing User and GetUserName is not implemented": {
456 session: &sessions.SessionState{
460 expectedEmail: sessionEmail,
464 for name, tc := range testCases {
465 t.Run(name, func(t *testing.T) {
466 opts := baseTestOptions()
467 err := validation.Validate(opts)
468 assert.NoError(t, err)
470 // intentionally set after validation.Validate(opts) since it will clobber
471 // our TestProvider and call `providers.New` defaulting to `providers.GoogleProvider`
472 opts.SetProvider(NewTestProvider(&url.URL{Host: "www.example.com"}, providerEmail))
473 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
478 err = proxy.enrichSessionState(context.Background(), tc.session)
479 assert.NoError(t, err)
480 assert.Equal(t, tc.expectedUser, tc.session.User)
481 assert.Equal(t, tc.expectedEmail, tc.session.Email)
486func TestBasicAuthPassword(t *testing.T) {
487 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
488 logger.Printf("%#v", r)
492 payload = `{"access_token": "my_auth_token"}`
494 payload = r.Header.Get("Authorization")
496 payload = "No Authorization header found."
500 _, err := w.Write([]byte(payload))
506 basicAuthPassword := "This is a secure password"
507 opts := baseTestOptions()
508 opts.UpstreamServers = options.Upstreams{
510 ID: providerServer.URL,
512 URI: providerServer.URL,
516 opts.Cookie.Secure = false
517 opts.InjectRequestHeaders = []options.Header{
519 Name: "Authorization",
520 Values: []options.HeaderValue{
522 ClaimSource: &options.ClaimSource{
524 BasicAuthPassword: &options.SecretSource{
525 Value: []byte(basicAuthPassword),
533 err := validation.Validate(opts)
534 assert.NoError(t, err)
536 providerURL, _ := url.Parse(providerServer.URL)
537 const emailAddress = "john.doe@example.com"
539 opts.SetProvider(NewTestProvider(providerURL, emailAddress))
540 proxy, err := NewOAuthProxy(opts, func(email string) bool {
541 return email == emailAddress
547 // Save the required session
548 rw := httptest.NewRecorder()
549 req, _ := http.NewRequest("GET", "/", nil)
550 err = proxy.sessionStore.Save(rw, req, &sessions.SessionState{
553 assert.NoError(t, err)
555 // Extract the cookie value to inject into the test request
556 cookie := rw.Header().Values("Set-Cookie")[0]
558 req, _ = http.NewRequest("GET", "/", nil)
559 req.Header.Set("Cookie", cookie)
560 rw = httptest.NewRecorder()
561 proxy.ServeHTTP(rw, req)
563 // The username in the basic auth credentials is expected to be equal to the email address from the
564 // auth response, so we use the same variable here.
565 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+basicAuthPassword))
566 assert.Equal(t, expectedHeader, rw.Body.String())
567 providerServer.Close()
570func TestPassGroupsHeadersWithGroups(t *testing.T) {
571 opts := baseTestOptions()
572 opts.InjectRequestHeaders = []options.Header{
574 Name: "X-Forwarded-Groups",
575 Values: []options.HeaderValue{
577 ClaimSource: &options.ClaimSource{
585 err := validation.Validate(opts)
586 assert.NoError(t, err)
588 const emailAddress = "john.doe@example.com"
589 const userName = "9fcab5c9b889a557"
591 groups := []string{"a", "b"}
592 created := time.Now()
593 session := &sessions.SessionState{
597 AccessToken: "oauth_token",
601 proxy, err := NewOAuthProxy(opts, func(email string) bool {
602 return email == emailAddress
604 assert.NoError(t, err)
606 // Save the required session
607 rw := httptest.NewRecorder()
608 req, _ := http.NewRequest("GET", "/", nil)
609 err = proxy.sessionStore.Save(rw, req, session)
610 assert.NoError(t, err)
612 // Extract the cookie value to inject into the test request
613 cookie := rw.Header().Values("Set-Cookie")[0]
615 req, _ = http.NewRequest("GET", "/", nil)
616 req.Header.Set("Cookie", cookie)
617 rw = httptest.NewRecorder()
618 proxy.ServeHTTP(rw, req)
620 assert.Equal(t, []string{"a,b"}, req.Header["X-Forwarded-Groups"])
623type PassAccessTokenTest struct {
624 providerServer *httptest.Server
626 opts *options.Options
629type PassAccessTokenTestOptions struct {
631 ProxyUpstream options.Upstream
634func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTest, error) {
635 patt := &PassAccessTokenTest{}
637 patt.providerServer = httptest.NewServer(
638 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
642 payload = `{"access_token": "my_auth_token"}`
644 payload = r.Header.Get("X-Forwarded-Access-Token")
646 payload = "No access token found."
650 _, err := w.Write([]byte(payload))
656 patt.opts = baseTestOptions()
657 patt.opts.UpstreamServers = options.Upstreams{
659 ID: patt.providerServer.URL,
661 URI: patt.providerServer.URL,
664 if opts.ProxyUpstream.ID != "" {
665 patt.opts.UpstreamServers = append(patt.opts.UpstreamServers, opts.ProxyUpstream)
668 patt.opts.Cookie.Secure = false
669 if opts.PassAccessToken {
670 patt.opts.InjectRequestHeaders = []options.Header{
672 Name: "X-Forwarded-Access-Token",
673 Values: []options.HeaderValue{
675 ClaimSource: &options.ClaimSource{
676 Claim: "access_token",
684 err := validation.Validate(patt.opts)
689 providerURL, _ := url.Parse(patt.providerServer.URL)
690 const emailAddress = "michael.bland@gsa.gov"
692 patt.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
693 patt.proxy, err = NewOAuthProxy(patt.opts, func(email string) bool {
694 return email == emailAddress
702func (patTest *PassAccessTokenTest) Close() {
703 patTest.providerServer.Close()
706func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
708 rw := httptest.NewRecorder()
709 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
710 strings.NewReader(""))
714 req.AddCookie(patTest.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
715 patTest.proxy.ServeHTTP(rw, req)
716 return rw.Code, rw.Header().Values("Set-Cookie")[1]
719// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
720// and cookie and returns body and status code.
721func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
722 cookieName := patTest.proxy.CookieName
724 keyPrefix := cookieName + "="
726 for _, field := range strings.Split(cookie, "; ") {
727 value = strings.TrimPrefix(field, keyPrefix)
738 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
742 req.AddCookie(&http.Cookie{
746 Expires: time.Now().Add(time.Duration(24)),
750 rw := httptest.NewRecorder()
751 patTest.proxy.ServeHTTP(rw, req)
752 return rw.Code, rw.Body.String()
755func TestForwardAccessTokenUpstream(t *testing.T) {
756 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
757 PassAccessToken: true,
762 t.Cleanup(patTest.Close)
764 // A successful validation will redirect and set the auth cookie.
765 code, cookie := patTest.getCallbackEndpoint()
767 t.Fatalf("expected 302; got %d", code)
769 assert.NotNil(t, cookie)
771 // Now we make a regular request; the access_token from the cookie is
772 // forwarded as the "X-Forwarded-Access-Token" header. The token is
773 // read by the test provider server and written in the response body.
774 code, payload := patTest.getEndpointWithCookie(cookie, "/")
776 t.Fatalf("expected 200; got %d", code)
778 assert.Equal(t, "my_auth_token", payload)
781func TestStaticProxyUpstream(t *testing.T) {
782 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
783 PassAccessToken: true,
784 ProxyUpstream: options.Upstream{
786 Path: "/static-proxy",
793 t.Cleanup(patTest.Close)
795 // A successful validation will redirect and set the auth cookie.
796 code, cookie := patTest.getCallbackEndpoint()
798 t.Fatalf("expected 302; got %d", code)
800 assert.NotEqual(t, nil, cookie)
802 // Now we make a regular request against the upstream proxy; And validate
803 // the returned status code through the static proxy.
804 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
806 t.Fatalf("expected 200; got %d", code)
808 assert.Equal(t, "Authenticated", payload)
811func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
812 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
813 PassAccessToken: false,
818 t.Cleanup(patTest.Close)
820 // A successful validation will redirect and set the auth cookie.
821 code, cookie := patTest.getCallbackEndpoint()
823 t.Fatalf("expected 302; got %d", code)
825 assert.NotEqual(t, nil, cookie)
827 // Now we make a regular request, but the access token header should
829 code, payload := patTest.getEndpointWithCookie(cookie, "/")
831 t.Fatalf("expected 200; got %d", code)
833 assert.Equal(t, "No access token found.", payload)
836type SignInPageTest struct {
837 opts *options.Options
839 signInRegexp *regexp.Regexp
840 signInProviderRegexp *regexp.Regexp
843const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
844const signInSkipProvider = `>Found<`
846func NewSignInPageTest(skipProvider bool) (*SignInPageTest, error) {
847 var sipTest SignInPageTest
849 sipTest.opts = baseTestOptions()
850 sipTest.opts.SkipProviderButton = skipProvider
851 err := validation.Validate(sipTest.opts)
856 sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
862 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
863 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
868func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
869 rw := httptest.NewRecorder()
870 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
871 sipTest.proxy.ServeHTTP(rw, req)
872 return rw.Code, rw.Body.String()
875func TestSignInPageIncludesTargetRedirect(t *testing.T) {
876 sipTest, err := NewSignInPageTest(false)
880 const endpoint = "/some/random/endpoint"
882 code, body := sipTest.GetEndpoint(endpoint)
883 assert.Equal(t, 403, code)
885 match := sipTest.signInRegexp.FindStringSubmatch(body)
887 t.Fatal("Did not find pattern in body: " +
888 signInRedirectPattern + "\nBody:\n" + body)
890 if match[1] != endpoint {
891 t.Fatal(`expected redirect to "` + endpoint +
892 `", but was "` + match[1] + `"`)
896func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
897 sipTest, err := NewSignInPageTest(false)
901 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
902 assert.Equal(t, 200, code)
904 match := sipTest.signInRegexp.FindStringSubmatch(body)
906 t.Fatal("Did not find pattern in body: " +
907 signInRedirectPattern + "\nBody:\n" + body)
910 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
914func TestSignInPageSkipProvider(t *testing.T) {
915 sipTest, err := NewSignInPageTest(true)
920 endpoint := "/some/random/endpoint"
922 code, body := sipTest.GetEndpoint(endpoint)
923 assert.Equal(t, 302, code)
925 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
927 t.Fatal("Did not find pattern in body: " +
928 signInSkipProvider + "\nBody:\n" + body)
932func TestSignInPageSkipProviderDirect(t *testing.T) {
933 sipTest, err := NewSignInPageTest(true)
938 endpoint := "/sign_in"
940 code, body := sipTest.GetEndpoint(endpoint)
941 assert.Equal(t, 302, code)
943 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
945 t.Fatal("Did not find pattern in body: " +
946 signInSkipProvider + "\nBody:\n" + body)
950type ProcessCookieTest struct {
951 opts *options.Options
953 rw *httptest.ResponseRecorder
958type ProcessCookieTestOpts struct {
959 providerValidateCookieResponse bool
962type OptionsModifier func(*options.Options)
964func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
965 var pcTest ProcessCookieTest
967 pcTest.opts = baseTestOptions()
968 for _, modifier := range modifiers {
969 modifier(pcTest.opts)
971 // First, set the CookieRefresh option so proxy.AesCipher is created,
972 // needed to encrypt the access_token.
973 pcTest.opts.Cookie.Refresh = time.Hour
974 err := validation.Validate(pcTest.opts)
979 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
980 return pcTest.validateUser
985 pcTest.proxy.provider = &TestProvider{
986 ProviderData: &providers.ProviderData{},
987 ValidToken: opts.providerValidateCookieResponse,
989 pcTest.proxy.provider.(*TestProvider).SetAllowedGroups(pcTest.opts.AllowedGroups)
991 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
992 // access_token validation.
993 pcTest.proxy.CookieRefresh = time.Duration(0)
994 pcTest.rw = httptest.NewRecorder()
995 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
996 pcTest.validateUser = true
1000func NewProcessCookieTestWithDefaults() (*ProcessCookieTest, error) {
1001 return NewProcessCookieTest(ProcessCookieTestOpts{
1002 providerValidateCookieResponse: true,
1006func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1007 return NewProcessCookieTest(ProcessCookieTestOpts{
1008 providerValidateCookieResponse: true,
1012func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
1013 err := p.proxy.SaveSession(p.rw, p.req, s)
1017 for _, cookie := range p.rw.Result().Cookies() {
1018 p.req.AddCookie(cookie)
1023func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
1024 return p.proxy.LoadCookiedSession(p.req)
1027func TestLoadCookiedSession(t *testing.T) {
1028 pcTest, err := NewProcessCookieTestWithDefaults()
1033 created := time.Now()
1034 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
1035 err = pcTest.SaveSession(startSession)
1036 assert.NoError(t, err)
1038 session, err := pcTest.LoadCookiedSession()
1042 assert.Equal(t, startSession.Email, session.Email)
1043 assert.Equal(t, "", session.User)
1044 assert.Equal(t, startSession.AccessToken, session.AccessToken)
1047func TestProcessCookieNoCookieError(t *testing.T) {
1048 pcTest, err := NewProcessCookieTestWithDefaults()
1053 session, err := pcTest.LoadCookiedSession()
1054 assert.Error(t, err, "cookie \"_oauth2_proxy\" not present")
1056 t.Errorf("expected nil session. got %#v", session)
1060func TestProcessCookieRefreshNotSet(t *testing.T) {
1061 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1062 opts.Cookie.Expire = time.Duration(23) * time.Hour
1068 reference := time.Now().Add(time.Duration(-2) * time.Hour)
1070 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1071 err = pcTest.SaveSession(startSession)
1072 assert.NoError(t, err)
1074 session, err := pcTest.LoadCookiedSession()
1075 assert.Equal(t, nil, err)
1076 if session.Age() < time.Duration(-2)*time.Hour {
1077 t.Errorf("cookie too young %v", session.Age())
1079 assert.Equal(t, startSession.Email, session.Email)
1082func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1083 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1084 opts.Cookie.Expire = time.Duration(24) * time.Hour
1090 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1091 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1092 err = pcTest.SaveSession(startSession)
1093 assert.NoError(t, err)
1095 session, err := pcTest.LoadCookiedSession()
1096 assert.NotEqual(t, nil, err)
1098 t.Errorf("expected nil session %#v", session)
1102func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1103 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1104 opts.Cookie.Expire = time.Duration(24) * time.Hour
1110 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1111 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1112 err = pcTest.SaveSession(startSession)
1113 assert.NoError(t, err)
1115 pcTest.proxy.CookieRefresh = time.Hour
1116 session, err := pcTest.LoadCookiedSession()
1117 assert.NotEqual(t, nil, err)
1119 t.Errorf("expected nil session %#v", session)
1123func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
1124 pcTest, err := NewProcessCookieTestWithDefaults()
1128 pcTest.req, _ = http.NewRequest("GET",
1129 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1133func TestUserInfoEndpointAccepted(t *testing.T) {
1134 testCases := []struct {
1136 session *sessions.SessionState
1137 expectedResponse string
1140 name: "Full session",
1141 session: &sessions.SessionState{
1143 Email: "john.doe@example.com",
1144 Groups: []string{"example", "groups"},
1145 AccessToken: "my_access_token",
1147 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n",
1150 name: "Minimal session",
1151 session: &sessions.SessionState{
1153 Email: "john.doe@example.com",
1154 Groups: []string{"example", "groups"},
1156 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n",
1160 session: &sessions.SessionState{
1162 Email: "john.doe@example.com",
1163 AccessToken: "my_access_token",
1165 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\"}\n",
1168 name: "With Preferred Username",
1169 session: &sessions.SessionState{
1171 PreferredUsername: "john",
1172 Email: "john.doe@example.com",
1173 Groups: []string{"example", "groups"},
1174 AccessToken: "my_access_token",
1176 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"],\"preferredUsername\":\"john\"}\n",
1180 for _, tc := range testCases {
1181 t.Run(tc.name, func(t *testing.T) {
1182 test, err := NewUserInfoEndpointTest()
1186 err = test.SaveSession(tc.session)
1187 assert.NoError(t, err)
1189 test.proxy.ServeHTTP(test.rw, test.req)
1190 assert.Equal(t, http.StatusOK, test.rw.Code)
1191 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1192 assert.Equal(t, tc.expectedResponse, string(bodyBytes))
1197func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1198 test, err := NewUserInfoEndpointTest()
1203 test.proxy.ServeHTTP(test.rw, test.req)
1204 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1207func NewAuthOnlyEndpointTest(querystring string, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1208 pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1212 pcTest.req, _ = http.NewRequest(
1214 fmt.Sprintf("%s/auth%s", pcTest.opts.ProxyPrefix, querystring),
1219func TestAuthOnlyEndpointAccepted(t *testing.T) {
1220 test, err := NewAuthOnlyEndpointTest("")
1225 created := time.Now()
1226 startSession := &sessions.SessionState{
1227 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1228 err = test.SaveSession(startSession)
1229 assert.NoError(t, err)
1231 test.proxy.ServeHTTP(test.rw, test.req)
1232 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1233 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1234 assert.Equal(t, "", string(bodyBytes))
1237func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1238 test, err := NewAuthOnlyEndpointTest("")
1243 test.proxy.ServeHTTP(test.rw, test.req)
1244 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1245 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1246 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1249func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1250 test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
1251 opts.Cookie.Expire = time.Duration(24) * time.Hour
1257 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1258 startSession := &sessions.SessionState{
1259 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1260 err = test.SaveSession(startSession)
1261 assert.NoError(t, err)
1263 test.proxy.ServeHTTP(test.rw, test.req)
1264 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1265 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1266 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1269func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1270 test, err := NewAuthOnlyEndpointTest("")
1275 created := time.Now()
1276 startSession := &sessions.SessionState{
1277 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1278 err = test.SaveSession(startSession)
1279 assert.NoError(t, err)
1280 test.validateUser = false
1282 test.proxy.ServeHTTP(test.rw, test.req)
1283 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1284 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1285 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1288func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1289 var pcTest ProcessCookieTest
1291 pcTest.opts = baseTestOptions()
1292 pcTest.opts.InjectResponseHeaders = []options.Header{
1294 Name: "X-Auth-Request-User",
1295 Values: []options.HeaderValue{
1297 ClaimSource: &options.ClaimSource{
1304 Name: "X-Auth-Request-Email",
1305 Values: []options.HeaderValue{
1307 ClaimSource: &options.ClaimSource{
1314 Name: "X-Auth-Request-Groups",
1315 Values: []options.HeaderValue{
1317 ClaimSource: &options.ClaimSource{
1324 Name: "X-Forwarded-Preferred-Username",
1325 Values: []options.HeaderValue{
1327 ClaimSource: &options.ClaimSource{
1328 Claim: "preferred_username",
1334 pcTest.opts.AllowedGroups = []string{"oauth_groups"}
1335 err := validation.Validate(pcTest.opts)
1336 assert.NoError(t, err)
1338 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1339 return pcTest.validateUser
1344 pcTest.proxy.provider = &TestProvider{
1345 ProviderData: &providers.ProviderData{},
1349 pcTest.validateUser = true
1351 pcTest.rw = httptest.NewRecorder()
1352 pcTest.req, _ = http.NewRequest("GET",
1353 pcTest.opts.ProxyPrefix+"/auth", nil)
1355 created := time.Now()
1356 startSession := &sessions.SessionState{
1357 User: "oauth_user", Groups: []string{"oauth_groups"}, Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1358 err = pcTest.SaveSession(startSession)
1359 assert.NoError(t, err)
1361 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1362 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1363 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1364 assert.Equal(t, startSession.Groups, pcTest.rw.Header().Values("X-Auth-Request-Groups"))
1365 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1368func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1369 var pcTest ProcessCookieTest
1371 pcTest.opts = baseTestOptions()
1372 pcTest.opts.InjectResponseHeaders = []options.Header{
1374 Name: "X-Auth-Request-User",
1375 Values: []options.HeaderValue{
1377 ClaimSource: &options.ClaimSource{
1384 Name: "X-Auth-Request-Email",
1385 Values: []options.HeaderValue{
1387 ClaimSource: &options.ClaimSource{
1394 Name: "X-Auth-Request-Groups",
1395 Values: []options.HeaderValue{
1397 ClaimSource: &options.ClaimSource{
1404 Name: "X-Forwarded-Preferred-Username",
1405 Values: []options.HeaderValue{
1407 ClaimSource: &options.ClaimSource{
1408 Claim: "preferred_username",
1414 Name: "Authorization",
1415 Values: []options.HeaderValue{
1417 ClaimSource: &options.ClaimSource{
1419 BasicAuthPassword: &options.SecretSource{
1420 Value: []byte("This is a secure password"),
1428 err := validation.Validate(pcTest.opts)
1429 assert.NoError(t, err)
1431 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1432 return pcTest.validateUser
1437 pcTest.proxy.provider = &TestProvider{
1438 ProviderData: &providers.ProviderData{},
1442 pcTest.validateUser = true
1444 pcTest.rw = httptest.NewRecorder()
1445 pcTest.req, _ = http.NewRequest("GET",
1446 pcTest.opts.ProxyPrefix+"/auth", nil)
1448 created := time.Now()
1449 startSession := &sessions.SessionState{
1450 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1451 err = pcTest.SaveSession(startSession)
1452 assert.NoError(t, err)
1454 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1455 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1456 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1457 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1458 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:This is a secure password"))
1459 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1462func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1463 var pcTest ProcessCookieTest
1465 pcTest.opts = baseTestOptions()
1466 pcTest.opts.InjectResponseHeaders = []options.Header{
1468 Name: "X-Auth-Request-User",
1469 Values: []options.HeaderValue{
1471 ClaimSource: &options.ClaimSource{
1478 Name: "X-Auth-Request-Email",
1479 Values: []options.HeaderValue{
1481 ClaimSource: &options.ClaimSource{
1488 Name: "X-Auth-Request-Groups",
1489 Values: []options.HeaderValue{
1491 ClaimSource: &options.ClaimSource{
1498 Name: "X-Forwarded-Preferred-Username",
1499 Values: []options.HeaderValue{
1501 ClaimSource: &options.ClaimSource{
1502 Claim: "preferred_username",
1508 err := validation.Validate(pcTest.opts)
1509 assert.NoError(t, err)
1511 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1512 return pcTest.validateUser
1517 pcTest.proxy.provider = &TestProvider{
1518 ProviderData: &providers.ProviderData{},
1522 pcTest.validateUser = true
1524 pcTest.rw = httptest.NewRecorder()
1525 pcTest.req, _ = http.NewRequest("GET",
1526 pcTest.opts.ProxyPrefix+"/auth", nil)
1528 created := time.Now()
1529 startSession := &sessions.SessionState{
1530 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1531 err = pcTest.SaveSession(startSession)
1532 assert.NoError(t, err)
1534 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1535 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1536 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1537 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1538 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1541func TestAuthSkippedForPreflightRequests(t *testing.T) {
1542 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1544 _, err := w.Write([]byte("response"))
1549 t.Cleanup(upstreamServer.Close)
1551 opts := baseTestOptions()
1552 opts.UpstreamServers = options.Upstreams{
1554 ID: upstreamServer.URL,
1556 URI: upstreamServer.URL,
1559 opts.SkipAuthPreflight = true
1560 err := validation.Validate(opts)
1561 assert.NoError(t, err)
1563 upstreamURL, _ := url.Parse(upstreamServer.URL)
1564 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1566 proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
1570 rw := httptest.NewRecorder()
1571 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1572 proxy.ServeHTTP(rw, req)
1574 assert.Equal(t, 200, rw.Code)
1575 assert.Equal(t, "response", rw.Body.String())
1578type SignatureAuthenticator struct {
1579 auth hmacauth.HmacAuth
1582func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1583 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1587 case hmacauth.ResultNoSignature:
1588 msg = "no signature received"
1589 case hmacauth.ResultMatch:
1590 msg = "signatures match"
1591 case hmacauth.ResultMismatch:
1593 "signatures do not match:\n received: %s\n computed: %s",
1597 panic("unknown result value: " + result.String())
1600 _, err := w.Write([]byte(msg))
1606type SignatureTest struct {
1607 opts *options.Options
1608 upstream *httptest.Server
1610 provider *httptest.Server
1612 rw *httptest.ResponseRecorder
1613 authenticator *SignatureAuthenticator
1616func NewSignatureTest() (*SignatureTest, error) {
1617 opts := baseTestOptions()
1618 opts.EmailDomains = []string{"acm.org"}
1620 authenticator := &SignatureAuthenticator{}
1621 upstreamServer := httptest.NewServer(
1622 http.HandlerFunc(authenticator.Authenticate))
1623 upstreamURL, err := url.Parse(upstreamServer.URL)
1627 opts.UpstreamServers = options.Upstreams{
1629 ID: upstreamServer.URL,
1631 URI: upstreamServer.URL,
1635 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1636 _, err := w.Write([]byte(`{"access_token": "my_auth_token"}`))
1641 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1642 providerURL, err := url.Parse(provider.URL)
1646 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1648 return &SignatureTest{
1654 httptest.NewRecorder(),
1659func (st *SignatureTest) Close() {
1664// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1665// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1666// https://github.com/18F/hmacauth/pull/4
1667type fakeNetConn struct {
1671func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1672 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1673 copy(p, fnc.reqBody)
1675 return bodyLen, io.EOF
1680func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) error {
1681 err := validation.Validate(st.opts)
1685 proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
1690 var bodyBuf io.ReadCloser
1692 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1694 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1695 req.Header = st.header
1697 state := &sessions.SessionState{
1698 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1699 err = proxy.SaveSession(st.rw, req, state)
1703 for _, c := range st.rw.Result().Cookies() {
1706 // This is used by the upstream to validate the signature.
1707 st.authenticator.auth = hmacauth.NewHmacAuth(
1708 crypto.SHA1, []byte(key), upstream.SignatureHeader, upstream.SignatureHeaders)
1709 proxy.ServeHTTP(st.rw, req)
1714func TestRequestSignature(t *testing.T) {
1715 testCases := map[string]struct {
1721 "No request signature": {
1725 resp: "no signature received",
1730 key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1731 resp: "signatures match",
1735 body: `{ "hello": "world!" }`,
1736 key: "d90df39e2d19282840252612dd7c81421a372f61",
1737 resp: "signatures match",
1740 for name, tc := range testCases {
1741 t.Run(name, func(t *testing.T) {
1742 st, err := NewSignatureTest()
1748 st.opts.SignatureKey = fmt.Sprintf("sha1:%s", tc.key)
1750 err = st.MakeRequestWithExpectedKey(tc.method, tc.body, tc.key)
1751 assert.NoError(t, err)
1752 assert.Equal(t, 200, st.rw.Code)
1753 assert.Equal(t, tc.resp, st.rw.Body.String())
1758func Test_getAppRedirect(t *testing.T) {
1759 opts := baseTestOptions()
1760 opts.WhitelistDomains = append(opts.WhitelistDomains, ".example.com", ".example.com:8443")
1761 err := validation.Validate(opts)
1762 assert.NoError(t, err)
1763 require.NotEmpty(t, opts.ProxyPrefix)
1764 proxy, err := NewOAuthProxy(opts, func(s string) bool { return false })
1772 headers map[string]string
1774 expectedRedirect string
1777 name: "request outside of ProxyPrefix redirects to original URL",
1780 reverseProxy: false,
1781 expectedRedirect: "/foo/bar",
1784 name: "request with query preserves query",
1787 reverseProxy: false,
1788 expectedRedirect: "/foo?bar",
1791 name: "request under ProxyPrefix redirects to root",
1792 url: proxy.ProxyPrefix + "/foo/bar",
1794 reverseProxy: false,
1795 expectedRedirect: "/",
1798 name: "proxied request outside of ProxyPrefix redirects to proxied URL",
1799 url: "https://oauth.example.com/foo/bar",
1800 headers: map[string]string{
1801 "X-Forwarded-Proto": "https",
1802 "X-Forwarded-Host": "a-service.example.com",
1803 "X-Forwarded-Uri": "/foo/bar",
1806 expectedRedirect: "https://a-service.example.com/foo/bar",
1809 name: "non-proxied request with spoofed proxy headers wouldn't redirect",
1810 url: "https://oauth.example.com/foo?bar",
1811 headers: map[string]string{
1812 "X-Forwarded-Proto": "https",
1813 "X-Forwarded-Host": "a-service.example.com",
1814 "X-Forwarded-Uri": "/foo/bar",
1816 reverseProxy: false,
1817 expectedRedirect: "/foo?bar",
1820 name: "proxied request under ProxyPrefix redirects to root",
1821 url: "https://oauth.example.com" + proxy.ProxyPrefix + "/foo/bar",
1822 headers: map[string]string{
1823 "X-Forwarded-Proto": "https",
1824 "X-Forwarded-Host": "a-service.example.com",
1825 "X-Forwarded-Uri": proxy.ProxyPrefix + "/foo/bar",
1828 expectedRedirect: "https://a-service.example.com/",
1831 name: "proxied request with port under ProxyPrefix redirects to root",
1832 url: "https://oauth.example.com" + proxy.ProxyPrefix + "/foo/bar",
1833 headers: map[string]string{
1834 "X-Forwarded-Proto": "https",
1835 "X-Forwarded-Host": "a-service.example.com:8443",
1836 "X-Forwarded-Uri": proxy.ProxyPrefix + "/foo/bar",
1839 expectedRedirect: "https://a-service.example.com:8443/",
1842 name: "proxied request with missing uri header would still redirect to desired redirect",
1843 url: "https://oauth.example.com/foo?bar",
1844 headers: map[string]string{
1845 "X-Forwarded-Proto": "https",
1846 "X-Forwarded-Host": "a-service.example.com",
1849 expectedRedirect: "https://a-service.example.com/foo?bar",
1852 name: "request with headers proxy not being set (and reverse proxy enabled) would still redirect to desired redirect",
1853 url: "https://oauth.example.com/foo?bar",
1856 expectedRedirect: "/foo?bar",
1859 name: "proxied request with X-Auth-Request-Redirect being set outside of ProxyPrefix redirects to proxied URL",
1860 url: "https://oauth.example.com/foo/bar",
1861 headers: map[string]string{
1862 "X-Auth-Request-Redirect": "https://a-service.example.com/foo/bar",
1865 expectedRedirect: "https://a-service.example.com/foo/bar",
1868 name: "proxied request with rd query string redirects to proxied URL",
1869 url: "https://oauth.example.com/foo/bar?rd=https%3A%2F%2Fa%2Dservice%2Eexample%2Ecom%2Ffoo%2Fbar",
1871 reverseProxy: false,
1872 expectedRedirect: "https://a-service.example.com/foo/bar",
1875 name: "proxied request with rd query string and all headers set (and reverse proxy not enabled) redirects to proxied URL on rd query string",
1876 url: "https://oauth.example.com/foo/bar?rd=https%3A%2F%2Fa%2Dservice%2Eexample%2Ecom%2Ffoo%2Fjazz",
1877 headers: map[string]string{
1878 "X-Auth-Request-Redirect": "https://a-service.example.com/foo/baz",
1879 "X-Forwarded-Proto": "http",
1880 "X-Forwarded-Host": "another-service.example.com",
1881 "X-Forwarded-Uri": "/seasons/greetings",
1883 reverseProxy: false,
1884 expectedRedirect: "https://a-service.example.com/foo/jazz",
1887 name: "proxied request with rd query string and some headers set redirects to proxied URL on rd query string",
1888 url: "https://oauth.example.com/foo/bar?rd=https%3A%2F%2Fa%2Dservice%2Eexample%2Ecom%2Ffoo%2Fbaz",
1889 headers: map[string]string{
1890 "X-Forwarded-Proto": "https",
1891 "X-Forwarded-Host": "another-service.example.com",
1892 "X-Forwarded-Uri": "/seasons/greetings",
1895 expectedRedirect: "https://a-service.example.com/foo/baz",
1898 for _, tt := range tests {
1899 t.Run(tt.name, func(t *testing.T) {
1900 req, _ := http.NewRequest("GET", tt.url, nil)
1901 for header, value := range tt.headers {
1903 req.Header.Add(header, value)
1906 req = middleware.AddRequestScope(req, &middleware.RequestScope{
1907 ReverseProxy: tt.reverseProxy,
1909 redirect, err := proxy.getAppRedirect(req)
1911 assert.NoError(t, err)
1912 assert.Equal(t, tt.expectedRedirect, redirect)
1917type ajaxRequestTest struct {
1918 opts *options.Options
1922func newAjaxRequestTest() (*ajaxRequestTest, error) {
1923 test := &ajaxRequestTest{}
1924 test.opts = baseTestOptions()
1925 err := validation.Validate(test.opts)
1930 test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
1939func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1940 rw := httptest.NewRecorder()
1941 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1946 test.proxy.ServeHTTP(rw, req)
1947 return rw.Code, rw.Header(), nil
1950func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1951 test, err := newAjaxRequestTest()
1957 code, rh, err := test.getEndpoint(endpoint, header)
1958 assert.NoError(t, err)
1959 assert.Equal(t, http.StatusUnauthorized, code)
1960 mime := rh.Get("Content-Type")
1961 assert.Equal(t, applicationJSON, mime)
1963func TestAjaxUnauthorizedRequest1(t *testing.T) {
1964 header := make(http.Header)
1965 header.Add("accept", applicationJSON)
1967 testAjaxUnauthorizedRequest(t, header)
1970func TestAjaxUnauthorizedRequest2(t *testing.T) {
1971 header := make(http.Header)
1972 header.Add("Accept", applicationJSON)
1974 testAjaxUnauthorizedRequest(t, header)
1977func TestAjaxUnauthorizedRequestAccept1(t *testing.T) {
1978 header := make(http.Header)
1979 header.Add("Accept", "application/json, text/plain, */*")
1981 testAjaxUnauthorizedRequest(t, header)
1984func TestAjaxForbiddendRequest(t *testing.T) {
1985 test, err := newAjaxRequestTest()
1990 header := make(http.Header)
1991 code, rh, err := test.getEndpoint(endpoint, header)
1992 assert.NoError(t, err)
1993 assert.Equal(t, http.StatusForbidden, code)
1994 mime := rh.Get("Content-Type")
1995 assert.NotEqual(t, applicationJSON, mime)
1998func TestClearSplitCookie(t *testing.T) {
1999 opts := baseTestOptions()
2000 opts.Cookie.Secret = base64CookieSecret
2001 opts.Cookie.Name = "oauth2"
2002 opts.Cookie.Domains = []string{"abc"}
2003 err := validation.Validate(opts)
2004 assert.NoError(t, err)
2006 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
2011 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
2012 var rw = httptest.NewRecorder()
2013 req := httptest.NewRequest("get", "/", nil)
2015 req.AddCookie(&http.Cookie{
2019 req.AddCookie(&http.Cookie{
2023 req.AddCookie(&http.Cookie{
2028 err = p.ClearSessionCookie(rw, req)
2029 assert.NoError(t, err)
2030 header := rw.Header()
2032 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
2035func TestClearSingleCookie(t *testing.T) {
2036 opts := baseTestOptions()
2037 opts.Cookie.Name = "oauth2"
2038 opts.Cookie.Domains = []string{"abc"}
2039 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
2044 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
2045 var rw = httptest.NewRecorder()
2046 req := httptest.NewRequest("get", "/", nil)
2048 req.AddCookie(&http.Cookie{
2052 req.AddCookie(&http.Cookie{
2057 err = p.ClearSessionCookie(rw, req)
2058 assert.NoError(t, err)
2059 header := rw.Header()
2061 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
2064type NoOpKeySet struct {
2067func (NoOpKeySet) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
2068 splitStrings := strings.Split(jwt, ".")
2069 payloadString := splitStrings[1]
2070 return base64.RawURLEncoding.DecodeString(payloadString)
2073func TestGetJwtSession(t *testing.T) {
2076 "sub": "1234567890",
2077 "aud": "https://test.myapp.com",
2079 "email": "john@example.com",
2080 "iss": "https://issuer.example.com",
2085 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
2086 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
2087 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
2088 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
2089 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
2090 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
2092 keyset := NoOpKeySet{}
2093 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
2094 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
2096 test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
2097 opts.InjectRequestHeaders = []options.Header{
2099 Name: "Authorization",
2100 Values: []options.HeaderValue{
2102 ClaimSource: &options.ClaimSource{
2110 Name: "X-Forwarded-User",
2111 Values: []options.HeaderValue{
2113 ClaimSource: &options.ClaimSource{
2120 Name: "X-Forwarded-Email",
2121 Values: []options.HeaderValue{
2123 ClaimSource: &options.ClaimSource{
2131 opts.InjectResponseHeaders = []options.Header{
2133 Name: "Authorization",
2134 Values: []options.HeaderValue{
2136 ClaimSource: &options.ClaimSource{
2144 Name: "X-Auth-Request-User",
2145 Values: []options.HeaderValue{
2147 ClaimSource: &options.ClaimSource{
2154 Name: "X-Auth-Request-Email",
2155 Values: []options.HeaderValue{
2157 ClaimSource: &options.ClaimSource{
2164 opts.SkipJwtBearerTokens = true
2165 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
2170 tp, _ := test.proxy.provider.(*TestProvider)
2171 tp.GroupValidator = func(s string) bool {
2175 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
2176 test.req.Header = map[string][]string{
2177 "Authorization": {authHeader},
2180 test.proxy.ServeHTTP(test.rw, test.req)
2181 if test.rw.Code >= 400 {
2182 t.Fatalf("expected 3xx got %d", test.rw.Code)
2185 // Check PassAuthorization, should overwrite Basic header
2186 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
2187 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "1234567890")
2188 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
2190 // SetAuthorization and SetXAuthRequest
2191 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
2192 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "1234567890")
2193 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
2196func Test_prepareNoCache(t *testing.T) {
2197 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2200 mux := http.NewServeMux()
2201 mux.Handle("/", handler)
2203 rec := httptest.NewRecorder()
2204 req := httptest.NewRequest(http.MethodGet, "/", nil)
2205 mux.ServeHTTP(rec, req)
2207 for k, v := range noCacheHeaders {
2208 assert.Equal(t, rec.Header().Get(k), v)
2212func Test_noCacheHeaders(t *testing.T) {
2213 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2214 _, err := w.Write([]byte("upstream"))
2219 t.Cleanup(upstreamServer.Close)
2221 opts := baseTestOptions()
2222 opts.UpstreamServers = options.Upstreams{
2224 ID: upstreamServer.URL,
2226 URI: upstreamServer.URL,
2229 opts.SkipAuthRegex = []string{".*"}
2230 err := validation.Validate(opts)
2231 assert.NoError(t, err)
2232 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2237 t.Run("not exist in response from upstream", func(t *testing.T) {
2238 rec := httptest.NewRecorder()
2239 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
2240 proxy.ServeHTTP(rec, req)
2242 assert.Equal(t, http.StatusOK, rec.Code)
2243 assert.Equal(t, "upstream", rec.Body.String())
2245 // checking noCacheHeaders does not exists in response headers from upstream
2246 for k := range noCacheHeaders {
2247 assert.Equal(t, "", rec.Header().Get(k))
2251 t.Run("has no-cache", func(t *testing.T) {
2257 path: "/oauth2/sign_in",
2261 path: "/oauth2/sign_out",
2265 path: "/oauth2/start",
2269 path: "/oauth2/callback",
2273 path: "/oauth2/auth",
2277 path: "/oauth2/userinfo",
2286 for _, tt := range tests {
2287 t.Run(tt.path, func(t *testing.T) {
2288 rec := httptest.NewRecorder()
2289 req := httptest.NewRequest(http.MethodGet, tt.path, nil)
2290 proxy.ServeHTTP(rec, req)
2291 cacheControl := rec.Result().Header.Get("Cache-Control")
2292 if tt.hasNoCache != (strings.Contains(cacheControl, "no-cache")) {
2293 t.Errorf(`unexpected "Cache-Control" header: %s`, cacheControl)
2301func baseTestOptions() *options.Options {
2302 opts := options.NewOptions()
2303 opts.Cookie.Secret = rawCookieSecret
2304 opts.ClientID = clientID
2305 opts.ClientSecret = clientSecret
2306 opts.EmailDomains = []string{"*"}
2308 // Default injected headers for legacy configuration
2309 opts.InjectRequestHeaders = []options.Header{
2311 Name: "Authorization",
2312 Values: []options.HeaderValue{
2314 ClaimSource: &options.ClaimSource{
2316 BasicAuthPassword: &options.SecretSource{
2317 Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
2324 Name: "X-Forwarded-User",
2325 Values: []options.HeaderValue{
2327 ClaimSource: &options.ClaimSource{
2334 Name: "X-Forwarded-Email",
2335 Values: []options.HeaderValue{
2337 ClaimSource: &options.ClaimSource{
2348func TestTrustedIPs(t *testing.T) {
2353 realClientIPHeader string
2357 // Check unconfigured behavior.
2361 reverseProxy: false,
2362 realClientIPHeader: "X-Real-IP", // Default value
2363 req: func() *http.Request {
2364 req, _ := http.NewRequest("GET", "/", nil)
2367 expectTrusted: false,
2369 // Check using req.RemoteAddr (Options.ReverseProxy == false).
2371 name: "WithRemoteAddr",
2372 trustedIPs: []string{"127.0.0.1"},
2373 reverseProxy: false,
2374 realClientIPHeader: "X-Real-IP", // Default value
2375 req: func() *http.Request {
2376 req, _ := http.NewRequest("GET", "/", nil)
2377 req.RemoteAddr = "127.0.0.1:43670"
2380 expectTrusted: true,
2382 // Check ignores req.RemoteAddr match when behind a reverse proxy / missing header.
2384 name: "IgnoresRemoteAddrInReverseProxyMode",
2385 trustedIPs: []string{"127.0.0.1"},
2387 realClientIPHeader: "X-Real-IP", // Default value
2388 req: func() *http.Request {
2389 req, _ := http.NewRequest("GET", "/", nil)
2390 req.RemoteAddr = "127.0.0.1:44324"
2393 expectTrusted: false,
2395 // Check successful trusting of localhost in IPv4.
2397 name: "TrustsLocalhostInReverseProxyMode",
2398 trustedIPs: []string{"127.0.0.0/8", "::1"},
2400 realClientIPHeader: "X-Forwarded-For",
2401 req: func() *http.Request {
2402 req, _ := http.NewRequest("GET", "/", nil)
2403 req.Header.Add("X-Forwarded-For", "127.0.0.1")
2406 expectTrusted: true,
2408 // Check successful trusting of localhost in IPv6.
2410 name: "TrustsIP6LocalostInReverseProxyMode",
2411 trustedIPs: []string{"127.0.0.0/8", "::1"},
2413 realClientIPHeader: "X-Forwarded-For",
2414 req: func() *http.Request {
2415 req, _ := http.NewRequest("GET", "/", nil)
2416 req.Header.Add("X-Forwarded-For", "::1")
2419 expectTrusted: true,
2421 // Check does not trust random IPv4 address.
2423 name: "DoesNotTrustRandomIP4Address",
2424 trustedIPs: []string{"127.0.0.0/8", "::1"},
2426 realClientIPHeader: "X-Forwarded-For",
2427 req: func() *http.Request {
2428 req, _ := http.NewRequest("GET", "/", nil)
2429 req.Header.Add("X-Forwarded-For", "12.34.56.78")
2432 expectTrusted: false,
2434 // Check does not trust random IPv6 address.
2436 name: "DoesNotTrustRandomIP6Address",
2437 trustedIPs: []string{"127.0.0.0/8", "::1"},
2439 realClientIPHeader: "X-Forwarded-For",
2440 req: func() *http.Request {
2441 req, _ := http.NewRequest("GET", "/", nil)
2442 req.Header.Add("X-Forwarded-For", "::2")
2445 expectTrusted: false,
2447 // Check respects correct header.
2449 name: "RespectsCorrectHeaderInReverseProxyMode",
2450 trustedIPs: []string{"127.0.0.0/8", "::1"},
2452 realClientIPHeader: "X-Forwarded-For",
2453 req: func() *http.Request {
2454 req, _ := http.NewRequest("GET", "/", nil)
2455 req.Header.Add("X-Real-IP", "::1")
2458 expectTrusted: false,
2460 // Check doesn't trust if garbage is provided.
2462 name: "DoesNotTrustGarbageInReverseProxyMode",
2463 trustedIPs: []string{"127.0.0.0/8", "::1"},
2465 realClientIPHeader: "X-Forwarded-For",
2466 req: func() *http.Request {
2467 req, _ := http.NewRequest("GET", "/", nil)
2468 req.Header.Add("X-Forwarded-For", "adsfljk29242as!!")
2471 expectTrusted: false,
2473 // Check doesn't trust if garbage is provided (no reverse-proxy).
2475 name: "DoesNotTrustGarbage",
2476 trustedIPs: []string{"127.0.0.0/8", "::1"},
2477 reverseProxy: false,
2478 realClientIPHeader: "X-Real-IP",
2479 req: func() *http.Request {
2480 req, _ := http.NewRequest("GET", "/", nil)
2481 req.RemoteAddr = "adsfljk29242as!!"
2484 expectTrusted: false,
2488 for _, tt := range tests {
2489 t.Run(tt.name, func(t *testing.T) {
2490 opts := baseTestOptions()
2491 opts.UpstreamServers = options.Upstreams{
2498 opts.TrustedIPs = tt.trustedIPs
2499 opts.ReverseProxy = tt.reverseProxy
2500 opts.RealClientIPHeader = tt.realClientIPHeader
2501 err := validation.Validate(opts)
2502 assert.NoError(t, err)
2504 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
2505 assert.NoError(t, err)
2506 rw := httptest.NewRecorder()
2508 proxy.ServeHTTP(rw, tt.req)
2509 if tt.expectTrusted {
2510 assert.Equal(t, 200, rw.Code)
2512 assert.Equal(t, 403, rw.Code)
2518func Test_buildRoutesAllowlist(t *testing.T) {
2519 type expectedAllowedRoute struct {
2524 testCases := []struct {
2526 skipAuthRegex []string
2527 skipAuthRoutes []string
2528 expectedRoutes []expectedAllowedRoute
2532 name: "No skip auth configured",
2533 skipAuthRegex: []string{},
2534 skipAuthRoutes: []string{},
2535 expectedRoutes: []expectedAllowedRoute{},
2539 name: "Only skipAuthRegex configured",
2540 skipAuthRegex: []string{
2542 "^/baz/[0-9]+/thing",
2544 skipAuthRoutes: []string{},
2545 expectedRoutes: []expectedAllowedRoute{
2548 regexString: "^/foo/bar",
2552 regexString: "^/baz/[0-9]+/thing",
2558 name: "Only skipAuthRoutes configured",
2559 skipAuthRegex: []string{},
2560 skipAuthRoutes: []string{
2562 "POST=^/baz/[0-9]+/thing",
2564 "WEIRD=^/methods/are/allowed",
2565 "PATCH=/second/equals?are=handled&just=fine",
2567 expectedRoutes: []expectedAllowedRoute{
2570 regexString: "^/foo/bar",
2574 regexString: "^/baz/[0-9]+/thing",
2578 regexString: "^/all/methods$",
2582 regexString: "^/methods/are/allowed",
2586 regexString: "/second/equals?are=handled&just=fine",
2592 name: "Both skipAuthRegexes and skipAuthRoutes configured",
2593 skipAuthRegex: []string{
2595 "^/baz/[0-9]+/thing/regex",
2597 skipAuthRoutes: []string{
2599 "POST=^/baz/[0-9]+/thing",
2602 expectedRoutes: []expectedAllowedRoute{
2605 regexString: "^/foo/bar/regex",
2609 regexString: "^/baz/[0-9]+/thing/regex",
2613 regexString: "^/foo/bar",
2617 regexString: "^/baz/[0-9]+/thing",
2621 regexString: "^/all/methods$",
2627 name: "Invalid skipAuthRegex entry",
2628 skipAuthRegex: []string{
2630 "^/baz/[0-9]+/thing",
2633 skipAuthRoutes: []string{},
2634 expectedRoutes: []expectedAllowedRoute{},
2638 name: "Invalid skipAuthRoutes entry",
2639 skipAuthRegex: []string{},
2640 skipAuthRoutes: []string{
2642 "POST=^/baz/[0-9]+/thing",
2646 expectedRoutes: []expectedAllowedRoute{},
2651 for _, tc := range testCases {
2652 t.Run(tc.name, func(t *testing.T) {
2653 opts := &options.Options{
2654 SkipAuthRegex: tc.skipAuthRegex,
2655 SkipAuthRoutes: tc.skipAuthRoutes,
2657 routes, err := buildRoutesAllowlist(opts)
2659 assert.Error(t, err)
2662 assert.NoError(t, err)
2664 for i, route := range routes {
2665 assert.Greater(t, len(tc.expectedRoutes), i)
2666 assert.Equal(t, route.method, tc.expectedRoutes[i].method)
2667 assert.Equal(t, route.pathRegex.String(), tc.expectedRoutes[i].regexString)
2673func TestAllowedRequest(t *testing.T) {
2674 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2676 _, err := w.Write([]byte("Allowed Request"))
2681 t.Cleanup(upstreamServer.Close)
2683 opts := baseTestOptions()
2684 opts.UpstreamServers = options.Upstreams{
2686 ID: upstreamServer.URL,
2688 URI: upstreamServer.URL,
2691 opts.SkipAuthRegex = []string{
2692 "^/skip/auth/regex$",
2694 opts.SkipAuthRoutes = []string{
2695 "GET=^/skip/auth/routes/get",
2697 err := validation.Validate(opts)
2698 assert.NoError(t, err)
2699 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2704 testCases := []struct {
2711 name: "Regex GET allowed",
2713 url: "/skip/auth/regex",
2717 name: "Regex POST allowed ",
2719 url: "/skip/auth/regex",
2723 name: "Regex denied",
2725 url: "/wrong/denied",
2729 name: "Route allowed",
2731 url: "/skip/auth/routes/get",
2735 name: "Route denied with wrong method",
2737 url: "/skip/auth/routes/get",
2741 name: "Route denied with wrong path",
2743 url: "/skip/auth/routes/wrong/path",
2748 for _, tc := range testCases {
2749 t.Run(tc.name, func(t *testing.T) {
2750 req, err := http.NewRequest(tc.method, tc.url, nil)
2751 assert.NoError(t, err)
2752 assert.Equal(t, tc.allowed, proxy.isAllowedRoute(req))
2754 rw := httptest.NewRecorder()
2755 proxy.ServeHTTP(rw, req)
2758 assert.Equal(t, 200, rw.Code)
2759 assert.Equal(t, "Allowed Request", rw.Body.String())
2761 assert.Equal(t, 403, rw.Code)
2767func TestProxyAllowedGroups(t *testing.T) {
2770 allowedGroups []string
2772 expectUnauthorized bool
2774 {"NoAllowedGroups", []string{}, []string{}, false},
2775 {"NoAllowedGroupsUserHasGroups", []string{}, []string{"a", "b"}, false},
2776 {"UserInAllowedGroup", []string{"a"}, []string{"a", "b"}, false},
2777 {"UserNotInAllowedGroup", []string{"a"}, []string{"c"}, true},
2780 for _, tt := range tests {
2781 t.Run(tt.name, func(t *testing.T) {
2782 emailAddress := "test"
2783 created := time.Now()
2785 session := &sessions.SessionState{
2787 Email: emailAddress,
2788 AccessToken: "oauth_token",
2789 CreatedAt: &created,
2792 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2795 t.Cleanup(upstreamServer.Close)
2797 test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
2798 opts.AllowedGroups = tt.allowedGroups
2799 opts.UpstreamServers = options.Upstreams{
2801 ID: upstreamServer.URL,
2803 URI: upstreamServer.URL,
2811 test.req, _ = http.NewRequest("GET", "/", nil)
2813 test.req.Header.Add("accept", applicationJSON)
2814 err = test.SaveSession(session)
2815 assert.NoError(t, err)
2816 test.proxy.ServeHTTP(test.rw, test.req)
2818 if tt.expectUnauthorized {
2819 assert.Equal(t, http.StatusForbidden, test.rw.Code)
2821 assert.Equal(t, http.StatusOK, test.rw.Code)
2827func TestAuthOnlyAllowedGroups(t *testing.T) {
2828 testCases := []struct {
2830 allowedGroups []string
2833 expectedStatusCode int
2836 name: "NoAllowedGroups",
2837 allowedGroups: []string{},
2840 expectedStatusCode: http.StatusAccepted,
2843 name: "NoAllowedGroupsUserHasGroups",
2844 allowedGroups: []string{},
2845 groups: []string{"a", "b"},
2847 expectedStatusCode: http.StatusAccepted,
2850 name: "UserInAllowedGroup",
2851 allowedGroups: []string{"a"},
2852 groups: []string{"a", "b"},
2854 expectedStatusCode: http.StatusAccepted,
2857 name: "UserNotInAllowedGroup",
2858 allowedGroups: []string{"a"},
2859 groups: []string{"c"},
2861 expectedStatusCode: http.StatusUnauthorized,
2864 name: "UserInQuerystringGroup",
2865 allowedGroups: []string{"a", "b"},
2866 groups: []string{"a", "c"},
2867 querystring: "?allowed_groups=a",
2868 expectedStatusCode: http.StatusAccepted,
2871 name: "UserInMultiParamQuerystringGroup",
2872 allowedGroups: []string{"a", "b"},
2873 groups: []string{"b"},
2874 querystring: "?allowed_groups=a&allowed_groups=b,d",
2875 expectedStatusCode: http.StatusAccepted,
2878 name: "UserInOnlyQuerystringGroup",
2879 allowedGroups: []string{},
2880 groups: []string{"a", "c"},
2881 querystring: "?allowed_groups=a,b",
2882 expectedStatusCode: http.StatusAccepted,
2885 name: "UserInDelimitedQuerystringGroup",
2886 allowedGroups: []string{"a", "b", "c"},
2887 groups: []string{"c"},
2888 querystring: "?allowed_groups=a,c",
2889 expectedStatusCode: http.StatusAccepted,
2892 name: "UserNotInQuerystringGroup",
2893 allowedGroups: []string{},
2894 groups: []string{"c"},
2895 querystring: "?allowed_groups=a,b",
2896 expectedStatusCode: http.StatusForbidden,
2899 name: "UserInConfigGroupNotInQuerystringGroup",
2900 allowedGroups: []string{"a", "b", "c"},
2901 groups: []string{"c"},
2902 querystring: "?allowed_groups=a,b",
2903 expectedStatusCode: http.StatusForbidden,
2906 name: "UserInQuerystringGroupNotInConfigGroup",
2907 allowedGroups: []string{"a", "b"},
2908 groups: []string{"c"},
2909 querystring: "?allowed_groups=b,c",
2910 expectedStatusCode: http.StatusUnauthorized,
2914 for _, tc := range testCases {
2915 t.Run(tc.name, func(t *testing.T) {
2916 emailAddress := "test"
2917 created := time.Now()
2919 session := &sessions.SessionState{
2921 Email: emailAddress,
2922 AccessToken: "oauth_token",
2923 CreatedAt: &created,
2926 test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {
2927 opts.AllowedGroups = tc.allowedGroups
2933 err = test.SaveSession(session)
2934 assert.NoError(t, err)
2936 test.proxy.ServeHTTP(test.rw, test.req)
2938 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