17 "github.com/coreos/go-oidc/v3/oidc"
18 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
19 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
20 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
21 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/hmacauth"
22 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
23 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
24 internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
25 sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
26 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
27 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
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(http.MethodGet, "/robots.txt", nil)
58 proxy.ServeHTTP(rw, req)
59 assert.Equal(t, 200, rw.Code)
60 assert.Equal(t, "User-agent: *\nDisallow: /\n", rw.Body.String())
63type TestProvider struct {
64 *providers.ProviderData
67 GroupValidator func(string) bool
70var _ providers.Provider = (*TestProvider)(nil)
72func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
74 ProviderData: &providers.ProviderData{
75 ProviderName: "Test Provider",
78 Host: providerURL.Host,
79 Path: "/oauth/authorize",
83 Host: providerURL.Host,
88 Host: providerURL.Host,
89 Path: "/api/v1/profile",
91 Scope: "profile.email",
93 EmailAddress: emailAddress,
94 GroupValidator: func(s string) bool {
100func (tp *TestProvider) GetEmailAddress(_ context.Context, _ *sessions.SessionState) (string, error) {
101 return tp.EmailAddress, nil
104func (tp *TestProvider) ValidateSession(_ context.Context, _ *sessions.SessionState) bool {
108func Test_redeemCode(t *testing.T) {
109 opts := baseTestOptions()
110 err := validation.Validate(opts)
111 assert.NoError(t, err)
113 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
118 req := httptest.NewRequest(http.MethodGet, "/", nil)
119 _, err = proxy.redeemCode(req, "")
120 assert.Equal(t, providers.ErrMissingCode, err)
123func Test_enrichSession(t *testing.T) {
125 sessionUser = "Mr Session"
126 sessionEmail = "session@example.com"
127 providerEmail = "provider@example.com"
130 testCases := map[string]struct {
131 session *sessions.SessionState
135 "Session already has enrichable fields": {
136 session: &sessions.SessionState{
140 expectedUser: sessionUser,
141 expectedEmail: sessionEmail,
143 "Session is missing Email and GetEmailAddress is implemented": {
144 session: &sessions.SessionState{
147 expectedUser: sessionUser,
148 expectedEmail: providerEmail,
150 "Session is missing User and GetUserName is not implemented": {
151 session: &sessions.SessionState{
155 expectedEmail: sessionEmail,
159 for name, tc := range testCases {
160 t.Run(name, func(t *testing.T) {
161 opts := baseTestOptions()
162 err := validation.Validate(opts)
163 assert.NoError(t, err)
165 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
169 proxy.provider = NewTestProvider(&url.URL{Host: "www.example.com"}, providerEmail)
171 err = proxy.enrichSessionState(context.Background(), tc.session)
172 assert.NoError(t, err)
173 assert.Equal(t, tc.expectedUser, tc.session.User)
174 assert.Equal(t, tc.expectedEmail, tc.session.Email)
179func TestBasicAuthPassword(t *testing.T) {
180 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
181 logger.Printf("%#v", r)
185 payload = `{"access_token": "my_auth_token"}`
187 payload = r.Header.Get("Authorization")
189 payload = "No Authorization header found."
193 _, err := w.Write([]byte(payload))
199 basicAuthPassword := "This is a secure password"
200 opts := baseTestOptions()
201 opts.UpstreamServers = options.UpstreamConfig{
202 Upstreams: []options.Upstream{
204 ID: providerServer.URL,
206 URI: providerServer.URL,
211 opts.Cookie.Secure = false
212 opts.InjectRequestHeaders = []options.Header{
214 Name: "Authorization",
215 Values: []options.HeaderValue{
217 ClaimSource: &options.ClaimSource{
219 BasicAuthPassword: &options.SecretSource{
220 Value: []byte(basicAuthPassword),
228 err := validation.Validate(opts)
229 assert.NoError(t, err)
231 providerURL, _ := url.Parse(providerServer.URL)
232 const emailAddress = "john.doe@example.com"
234 proxy, err := NewOAuthProxy(opts, func(email string) bool {
235 return email == emailAddress
240 proxy.provider = NewTestProvider(providerURL, emailAddress)
242 // Save the required session
243 rw := httptest.NewRecorder()
244 req, _ := http.NewRequest(http.MethodGet, "/", nil)
245 err = proxy.sessionStore.Save(rw, req, &sessions.SessionState{
248 assert.NoError(t, err)
250 // Extract the cookie value to inject into the test request
251 cookie := rw.Header().Values("Set-Cookie")[0]
253 req, _ = http.NewRequest(http.MethodGet, "/", nil)
254 req.Header.Set("Cookie", cookie)
255 rw = httptest.NewRecorder()
256 proxy.ServeHTTP(rw, req)
258 // The username in the basic auth credentials is expected to be equal to the email address from the
259 // auth response, so we use the same variable here.
260 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+basicAuthPassword))
261 assert.Equal(t, expectedHeader, rw.Body.String())
262 providerServer.Close()
265func TestPassGroupsHeadersWithGroups(t *testing.T) {
266 opts := baseTestOptions()
267 opts.InjectRequestHeaders = []options.Header{
269 Name: "X-Forwarded-Groups",
270 Values: []options.HeaderValue{
272 ClaimSource: &options.ClaimSource{
280 err := validation.Validate(opts)
281 assert.NoError(t, err)
283 const emailAddress = "john.doe@example.com"
284 const userName = "9fcab5c9b889a557"
286 groups := []string{"a", "b"}
287 created := time.Now()
288 session := &sessions.SessionState{
292 AccessToken: "oauth_token",
296 proxy, err := NewOAuthProxy(opts, func(email string) bool {
297 return email == emailAddress
299 assert.NoError(t, err)
301 // Save the required session
302 rw := httptest.NewRecorder()
303 req, _ := http.NewRequest(http.MethodGet, "/", nil)
304 err = proxy.sessionStore.Save(rw, req, session)
305 assert.NoError(t, err)
307 // Extract the cookie value to inject into the test request
308 cookie := rw.Header().Values("Set-Cookie")[0]
310 req, _ = http.NewRequest(http.MethodGet, "/", nil)
311 req.Header.Set("Cookie", cookie)
312 rw = httptest.NewRecorder()
313 proxy.ServeHTTP(rw, req)
315 assert.Equal(t, []string{"a,b"}, req.Header["X-Forwarded-Groups"])
318type PassAccessTokenTest struct {
319 providerServer *httptest.Server
321 opts *options.Options
324type PassAccessTokenTestOptions struct {
327 ProxyUpstream options.Upstream
330func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTest, error) {
331 patt := &PassAccessTokenTest{}
333 patt.providerServer = httptest.NewServer(
334 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
338 payload = `{"access_token": "my_auth_token"}`
340 payload = r.Header.Get("X-Forwarded-Access-Token")
342 payload = "No access token found."
346 _, err := w.Write([]byte(payload))
352 patt.opts = baseTestOptions()
353 patt.opts.UpstreamServers = options.UpstreamConfig{
354 Upstreams: []options.Upstream{
356 ID: patt.providerServer.URL,
358 URI: patt.providerServer.URL,
362 if opts.ProxyUpstream.ID != "" {
363 patt.opts.UpstreamServers.Upstreams = append(patt.opts.UpstreamServers.Upstreams, opts.ProxyUpstream)
366 patt.opts.Cookie.Secure = false
367 if opts.PassAccessToken {
368 patt.opts.InjectRequestHeaders = []options.Header{
370 Name: "X-Forwarded-Access-Token",
371 Values: []options.HeaderValue{
373 ClaimSource: &options.ClaimSource{
374 Claim: "access_token",
382 err := validation.Validate(patt.opts)
387 providerURL, _ := url.Parse(patt.providerServer.URL)
388 const emailAddress = "michael.bland@gsa.gov"
390 testProvider := NewTestProvider(providerURL, emailAddress)
391 testProvider.ValidToken = opts.ValidToken
392 patt.proxy, err = NewOAuthProxy(patt.opts, func(email string) bool {
393 return email == emailAddress
395 patt.proxy.provider = testProvider
402func (patTest *PassAccessTokenTest) Close() {
403 patTest.providerServer.Close()
406func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int, cookie string) {
407 rw := httptest.NewRecorder()
409 csrf, err := cookies.NewCSRF(patTest.proxy.CookieOptions, "")
414 req, err := http.NewRequest(
417 "/oauth2/callback?code=callback_code&state=%s",
418 encodeState(csrf.HashOAuthState(), "%2F", false),
420 strings.NewReader(""),
426 // rw is a dummy here, we just want the csrfCookie to add to our req
427 csrfCookie, err := csrf.SetCookie(httptest.NewRecorder(), req)
431 req.AddCookie(csrfCookie)
433 patTest.proxy.ServeHTTP(rw, req)
435 if len(rw.Header().Values("Set-Cookie")) >= 2 {
436 cookie = rw.Header().Values("Set-Cookie")[1]
439 return rw.Code, cookie
442// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
443// and cookie and returns body and status code.
444func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
445 cookieName := patTest.proxy.CookieOptions.Name
447 keyPrefix := cookieName + "="
449 for _, field := range strings.Split(cookie, "; ") {
450 value = strings.TrimPrefix(field, keyPrefix)
460 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
464 req.AddCookie(&http.Cookie{
468 Expires: time.Now().Add(time.Duration(24)),
472 rw := httptest.NewRecorder()
473 patTest.proxy.ServeHTTP(rw, req)
474 return rw.Code, rw.Body.String()
477func TestForwardAccessTokenUpstream(t *testing.T) {
478 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
479 PassAccessToken: true,
485 t.Cleanup(patTest.Close)
487 // A successful validation will redirect and set the auth cookie.
488 code, cookie := patTest.getCallbackEndpoint()
490 t.Fatalf("expected 302; got %d", code)
492 assert.NotNil(t, cookie)
494 // Now we make a regular request; the access_token from the cookie is
495 // forwarded as the "X-Forwarded-Access-Token" header. The token is
496 // read by the test provider server and written in the response body.
497 code, payload := patTest.getEndpointWithCookie(cookie, "/")
499 t.Fatalf("expected 200; got %d", code)
501 assert.Equal(t, "my_auth_token", payload)
504func TestStaticProxyUpstream(t *testing.T) {
505 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
506 PassAccessToken: true,
508 ProxyUpstream: options.Upstream{
510 Path: "/static-proxy",
511 Static: ptr.To(true),
517 t.Cleanup(patTest.Close)
519 // A successful validation will redirect and set the auth cookie.
520 code, cookie := patTest.getCallbackEndpoint()
522 t.Fatalf("expected 302; got %d", code)
524 assert.NotEqual(t, nil, cookie)
526 // Now we make a regular request against the upstream proxy; And validate
527 // the returned status code through the static proxy.
528 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
530 t.Fatalf("expected 200; got %d", code)
532 assert.Equal(t, "Authenticated", payload)
535func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
536 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
537 PassAccessToken: false,
543 t.Cleanup(patTest.Close)
545 // A successful validation will redirect and set the auth cookie.
546 code, cookie := patTest.getCallbackEndpoint()
548 t.Fatalf("expected 302; got %d", code)
550 assert.NotEqual(t, nil, cookie)
552 // Now we make a regular request, but the access token header should
554 code, payload := patTest.getEndpointWithCookie(cookie, "/")
556 t.Fatalf("expected 200; got %d", code)
558 assert.Equal(t, "No access token found.", payload)
561func TestSessionValidationFailure(t *testing.T) {
562 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
565 require.NoError(t, err)
566 t.Cleanup(patTest.Close)
568 // An unsuccessful validation will return 403 and not set the auth cookie.
569 code, cookie := patTest.getCallbackEndpoint()
570 assert.Equal(t, http.StatusForbidden, code)
571 assert.Equal(t, "", cookie)
574type SignInPageTest struct {
575 opts *options.Options
577 signInRegexp *regexp.Regexp
578 signInProviderRegexp *regexp.Regexp
582 signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
583 signInSkipProvider = `>Found<`
584 patternNotFound = "Did not find pattern in body: "
587func NewSignInPageTest(skipProvider bool) (*SignInPageTest, error) {
588 var sipTest SignInPageTest
590 sipTest.opts = baseTestOptions()
591 sipTest.opts.SkipProviderButton = skipProvider
592 err := validation.Validate(sipTest.opts)
597 sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
603 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
604 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
609func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
610 rw := httptest.NewRecorder()
611 req, _ := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
612 sipTest.proxy.ServeHTTP(rw, req)
613 return rw.Code, rw.Body.String()
616type AlwaysSuccessfulValidator struct {
619func (AlwaysSuccessfulValidator) Validate(_, _ string) bool {
623func TestManualSignInStoresUserGroupsInTheSession(t *testing.T) {
624 userGroups := []string{"somegroup", "someothergroup"}
626 opts := baseTestOptions()
627 opts.HtpasswdUserGroups = userGroups
628 err := validation.Validate(opts)
633 proxy, err := NewOAuthProxy(opts, func(email string) bool {
639 proxy.basicAuthValidator = AlwaysSuccessfulValidator{}
641 rw := httptest.NewRecorder()
642 formData := url.Values{}
643 formData.Set("username", "someuser")
644 formData.Set("password", "somepass")
645 signInReq, _ := http.NewRequest(http.MethodPost, "/oauth2/sign_in", strings.NewReader(formData.Encode()))
646 signInReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
647 proxy.ServeHTTP(rw, signInReq)
649 assert.Equal(t, http.StatusFound, rw.Code)
651 req, _ := http.NewRequest(http.MethodGet, "/something", strings.NewReader(formData.Encode()))
652 for _, c := range rw.Result().Cookies() {
656 s, err := proxy.sessionStore.Load(req)
660 assert.Equal(t, userGroups, s.Groups)
663type ManualSignInValidator struct{}
665func (ManualSignInValidator) Validate(user, password string) bool {
667 case user == "admin" && password == "adminPass":
674func ManualSignInWithCredentials(t *testing.T, user, pass string) int {
675 opts := baseTestOptions()
676 err := validation.Validate(opts)
681 proxy, err := NewOAuthProxy(opts, func(email string) bool {
688 proxy.basicAuthValidator = ManualSignInValidator{}
690 rw := httptest.NewRecorder()
691 formData := url.Values{}
692 formData.Set("username", user)
693 formData.Set("password", pass)
694 signInReq, _ := http.NewRequest(http.MethodPost, "/oauth2/sign_in", strings.NewReader(formData.Encode()))
695 signInReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
696 proxy.ServeHTTP(rw, signInReq)
701func TestManualSignInEmptyUsernameAlert(t *testing.T) {
702 statusCode := ManualSignInWithCredentials(t, "", "")
703 assert.Equal(t, http.StatusBadRequest, statusCode)
706func TestManualSignInInvalidCredentialsAlert(t *testing.T) {
707 statusCode := ManualSignInWithCredentials(t, "admin", "")
708 assert.Equal(t, http.StatusUnauthorized, statusCode)
711func TestManualSignInCorrectCredentials(t *testing.T) {
712 statusCode := ManualSignInWithCredentials(t, "admin", "adminPass")
713 assert.Equal(t, http.StatusFound, statusCode)
716func TestSignInPageClearsExistingSessionCookie(t *testing.T) {
717 opts := baseTestOptions()
718 err := validation.Validate(opts)
719 require.NoError(t, err)
721 proxy, err := NewOAuthProxy(opts, func(string) bool {
724 require.NoError(t, err)
726 // Create a real session cookie using the actual session store.
727 saveRW := httptest.NewRecorder()
728 saveReq := httptest.NewRequest(http.MethodGet, "/", nil)
729 err = proxy.sessionStore.Save(saveRW, saveReq, &sessions.SessionState{
730 Email: "john.doe@example.com",
732 require.NoError(t, err)
734 cookies := saveRW.Result().Cookies()
735 require.NotEmpty(t, cookies)
737 // Send that cookie to the sign-in page.
738 req := httptest.NewRequest(http.MethodGet, "/oauth2/sign_in", nil)
739 for _, c := range cookies {
743 rw := httptest.NewRecorder()
744 proxy.ServeHTTP(rw, req)
746 assert.Equal(t, http.StatusOK, rw.Code)
749 for _, c := range rw.Result().Cookies() {
750 if c.Name == proxy.CookieOptions.Name {
752 assert.Equal(t, "", c.Value)
753 assert.Less(t, c.MaxAge, 0)
757 assert.True(t, cleared, "expected sign-in page to clear existing session cookie")
760func TestSignInPageIncludesTargetRedirect(t *testing.T) {
761 sipTest, err := NewSignInPageTest(false)
765 const endpoint = "/some/random/endpoint"
767 code, body := sipTest.GetEndpoint(endpoint)
768 assert.Equal(t, 403, code)
770 match := sipTest.signInRegexp.FindStringSubmatch(body)
772 t.Fatal(patternNotFound +
773 signInRedirectPattern + "\nBody:\n" + body)
775 if match[1] != endpoint {
776 t.Fatal(`expected redirect to "` + endpoint +
777 `", but was "` + match[1] + `"`)
781func TestSignInPageInvalidQueryStringReturnsBadRequest(t *testing.T) {
782 sipTest, err := NewSignInPageTest(true)
786 const endpoint = "/?q=%va"
788 code, _ := sipTest.GetEndpoint(endpoint)
789 assert.Equal(t, 400, code)
792func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
793 sipTest, err := NewSignInPageTest(false)
797 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
798 assert.Equal(t, 200, code)
800 match := sipTest.signInRegexp.FindStringSubmatch(body)
802 t.Fatal(patternNotFound +
803 signInRedirectPattern + "\nBody:\n" + body)
806 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
810func TestSignInPageSkipProvider(t *testing.T) {
811 sipTest, err := NewSignInPageTest(true)
816 endpoint := "/some/random/endpoint"
818 code, body := sipTest.GetEndpoint(endpoint)
819 assert.Equal(t, 302, code)
821 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
823 t.Fatal(patternNotFound +
824 signInSkipProvider + "\nBody:\n" + body)
828func TestSignInPageSkipProviderDirect(t *testing.T) {
829 sipTest, err := NewSignInPageTest(true)
834 endpoint := "/sign_in"
836 code, body := sipTest.GetEndpoint(endpoint)
837 assert.Equal(t, 302, code)
839 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
841 t.Fatal(patternNotFound +
842 signInSkipProvider + "\nBody:\n" + body)
846type ProcessCookieTest struct {
847 opts *options.Options
849 rw *httptest.ResponseRecorder
854type ProcessCookieTestOpts struct {
855 providerValidateCookieResponse bool
858type OptionsModifier func(*options.Options)
860func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
861 var pcTest ProcessCookieTest
863 pcTest.opts = baseTestOptions()
864 for _, modifier := range modifiers {
865 modifier(pcTest.opts)
867 // First, set the CookieRefresh option so proxy.AesCipher is created,
868 // needed to encrypt the access_token.
869 pcTest.opts.Cookie.Refresh = time.Hour
870 err := validation.Validate(pcTest.opts)
875 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
876 return pcTest.validateUser
881 testProvider := &TestProvider{
882 ProviderData: &providers.ProviderData{},
883 ValidToken: opts.providerValidateCookieResponse,
886 groups := pcTest.opts.Providers[0].AllowedGroups
887 testProvider.ProviderData.AllowedGroups = make(map[string]struct{}, len(groups))
888 for _, group := range groups {
889 testProvider.ProviderData.AllowedGroups[group] = struct{}{}
891 pcTest.proxy.provider = testProvider
893 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
894 // access_token validation.
895 pcTest.proxy.CookieOptions.Refresh = time.Duration(0)
896 pcTest.rw = httptest.NewRecorder()
897 pcTest.req, _ = http.NewRequest(http.MethodGet, "/", strings.NewReader(""))
898 pcTest.validateUser = true
902func NewProcessCookieTestWithDefaults() (*ProcessCookieTest, error) {
903 return NewProcessCookieTest(ProcessCookieTestOpts{
904 providerValidateCookieResponse: true,
908func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
909 return NewProcessCookieTest(ProcessCookieTestOpts{
910 providerValidateCookieResponse: true,
914func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
915 err := p.proxy.SaveSession(p.rw, p.req, s)
919 for _, cookie := range p.rw.Result().Cookies() {
920 p.req.AddCookie(cookie)
925func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
926 return p.proxy.LoadCookiedSession(p.req)
929func TestLoadCookiedSession(t *testing.T) {
930 pcTest, err := NewProcessCookieTestWithDefaults()
935 created := time.Now()
936 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
937 err = pcTest.SaveSession(startSession)
938 assert.NoError(t, err)
940 session, err := pcTest.LoadCookiedSession()
944 assert.Equal(t, startSession.Email, session.Email)
945 assert.Equal(t, "", session.User)
946 assert.Equal(t, startSession.AccessToken, session.AccessToken)
949func TestProcessCookieNoCookieError(t *testing.T) {
950 pcTest, err := NewProcessCookieTestWithDefaults()
955 session, err := pcTest.LoadCookiedSession()
956 assert.Error(t, err, "cookie \"_oauth2_proxy\" not present")
958 t.Errorf("expected nil session. got %#v", session)
962func TestProcessCookieRefreshNotSet(t *testing.T) {
963 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
964 opts.Cookie.Expire = time.Duration(23) * time.Hour
970 reference := time.Now().Add(time.Duration(-2) * time.Hour)
972 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
973 err = pcTest.SaveSession(startSession)
974 assert.NoError(t, err)
976 session, err := pcTest.LoadCookiedSession()
977 assert.Equal(t, nil, err)
978 if session.Age() < time.Duration(-2)*time.Hour {
979 t.Errorf("cookie too young %v", session.Age())
981 assert.Equal(t, startSession.Email, session.Email)
984func TestProcessCookieFailIfCookieExpired(t *testing.T) {
985 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
986 opts.Cookie.Expire = time.Duration(24) * time.Hour
992 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
993 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
994 err = pcTest.SaveSession(startSession)
995 assert.NoError(t, err)
997 session, err := pcTest.LoadCookiedSession()
998 assert.NotEqual(t, nil, err)
1000 t.Errorf("expected nil session %#v", session)
1004func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1005 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1006 opts.Cookie.Expire = time.Duration(24) * time.Hour
1012 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1013 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1014 err = pcTest.SaveSession(startSession)
1015 assert.NoError(t, err)
1017 pcTest.proxy.CookieOptions.Refresh = time.Hour
1018 session, err := pcTest.LoadCookiedSession()
1019 assert.NotEqual(t, nil, err)
1021 t.Errorf("expected nil session %#v", session)
1025func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
1026 pcTest, err := NewProcessCookieTestWithDefaults()
1030 pcTest.req, _ = http.NewRequest(http.MethodGet,
1031 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1035func TestUserInfoEndpointAccepted(t *testing.T) {
1036 testCases := []struct {
1038 session *sessions.SessionState
1039 expectedResponse string
1042 name: "Full session",
1043 session: &sessions.SessionState{
1045 Email: "john.doe@example.com",
1046 Groups: []string{"example", "groups"},
1047 AccessToken: "my_access_token",
1049 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n",
1052 name: "Minimal session",
1053 session: &sessions.SessionState{
1055 Email: "john.doe@example.com",
1056 Groups: []string{"example", "groups"},
1058 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n",
1062 session: &sessions.SessionState{
1064 Email: "john.doe@example.com",
1065 AccessToken: "my_access_token",
1067 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\"}\n",
1070 name: "With Preferred Username",
1071 session: &sessions.SessionState{
1073 PreferredUsername: "john",
1074 Email: "john.doe@example.com",
1075 Groups: []string{"example", "groups"},
1076 AccessToken: "my_access_token",
1078 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"],\"preferredUsername\":\"john\"}\n",
1081 name: "With Additional Claim",
1082 session: &sessions.SessionState{
1084 PreferredUsername: "john",
1085 Email: "john.doe@example.com",
1086 Groups: []string{"example", "groups"},
1087 AccessToken: "my_access_token",
1088 AdditionalClaims: map[string]interface{}{
1092 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"],\"preferredUsername\":\"john\",\"additionalClaims\":{\"foo\":\"bar\"}}\n",
1096 for _, tc := range testCases {
1097 t.Run(tc.name, func(t *testing.T) {
1098 test, err := NewUserInfoEndpointTest()
1102 err = test.SaveSession(tc.session)
1103 assert.NoError(t, err)
1105 test.proxy.ServeHTTP(test.rw, test.req)
1106 assert.Equal(t, http.StatusOK, test.rw.Code)
1107 bodyBytes, _ := io.ReadAll(test.rw.Body)
1108 assert.Equal(t, tc.expectedResponse, string(bodyBytes))
1113func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1114 test, err := NewUserInfoEndpointTest()
1119 test.proxy.ServeHTTP(test.rw, test.req)
1120 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1123func TestEncodedUrlsStayEncoded(t *testing.T) {
1124 encodeTest, err := NewSignInPageTest(false)
1128 code, _ := encodeTest.GetEndpoint("/%2F/test1/%2F/test2")
1129 assert.Equal(t, 403, code)
1132func NewAuthOnlyEndpointTest(querystring string, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1133 pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1137 pcTest.req, _ = http.NewRequest(
1139 fmt.Sprintf("%s/auth%s", pcTest.opts.ProxyPrefix, querystring),
1144func TestAuthOnlyEndpointAccepted(t *testing.T) {
1145 test, err := NewAuthOnlyEndpointTest("")
1150 created := time.Now()
1151 startSession := &sessions.SessionState{
1152 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1153 err = test.SaveSession(startSession)
1154 assert.NoError(t, err)
1156 test.proxy.ServeHTTP(test.rw, test.req)
1157 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1158 bodyBytes, _ := io.ReadAll(test.rw.Body)
1159 assert.Equal(t, "", string(bodyBytes))
1162func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1163 test, err := NewAuthOnlyEndpointTest("")
1168 test.proxy.ServeHTTP(test.rw, test.req)
1169 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1170 bodyBytes, _ := io.ReadAll(test.rw.Body)
1171 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1174func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1175 test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
1176 opts.Cookie.Expire = time.Duration(24) * time.Hour
1182 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1183 startSession := &sessions.SessionState{
1184 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1185 err = test.SaveSession(startSession)
1186 assert.NoError(t, err)
1188 test.proxy.ServeHTTP(test.rw, test.req)
1189 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1190 bodyBytes, _ := io.ReadAll(test.rw.Body)
1191 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1194func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1195 test, err := NewAuthOnlyEndpointTest("")
1200 created := time.Now()
1201 startSession := &sessions.SessionState{
1202 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1203 err = test.SaveSession(startSession)
1204 assert.NoError(t, err)
1205 test.validateUser = false
1207 test.proxy.ServeHTTP(test.rw, test.req)
1208 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1209 bodyBytes, _ := io.ReadAll(test.rw.Body)
1210 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1213func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1214 var pcTest ProcessCookieTest
1216 pcTest.opts = baseTestOptions()
1217 pcTest.opts.InjectResponseHeaders = []options.Header{
1219 Name: "X-Auth-Request-User",
1220 Values: []options.HeaderValue{
1222 ClaimSource: &options.ClaimSource{
1229 Name: "X-Auth-Request-Email",
1230 Values: []options.HeaderValue{
1232 ClaimSource: &options.ClaimSource{
1239 Name: "X-Auth-Request-Groups",
1240 Values: []options.HeaderValue{
1242 ClaimSource: &options.ClaimSource{
1249 Name: "X-Forwarded-Preferred-Username",
1250 Values: []options.HeaderValue{
1252 ClaimSource: &options.ClaimSource{
1253 Claim: "preferred_username",
1259 pcTest.opts.Providers[0].AllowedGroups = []string{"oauth_groups"}
1260 err := validation.Validate(pcTest.opts)
1261 assert.NoError(t, err)
1263 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1264 return pcTest.validateUser
1269 pcTest.proxy.provider = &TestProvider{
1270 ProviderData: &providers.ProviderData{},
1274 pcTest.validateUser = true
1276 pcTest.rw = httptest.NewRecorder()
1277 pcTest.req, _ = http.NewRequest(http.MethodGet,
1278 pcTest.opts.ProxyPrefix+authOnlyPath, nil)
1280 created := time.Now()
1281 startSession := &sessions.SessionState{
1282 User: "oauth_user", Groups: []string{"oauth_groups"}, Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1283 err = pcTest.SaveSession(startSession)
1284 assert.NoError(t, err)
1286 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1287 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1288 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1289 assert.Equal(t, startSession.Groups, pcTest.rw.Header().Values("X-Auth-Request-Groups"))
1290 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1293func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1294 var pcTest ProcessCookieTest
1296 pcTest.opts = baseTestOptions()
1297 pcTest.opts.InjectResponseHeaders = []options.Header{
1299 Name: "X-Auth-Request-User",
1300 Values: []options.HeaderValue{
1302 ClaimSource: &options.ClaimSource{
1309 Name: "X-Auth-Request-Email",
1310 Values: []options.HeaderValue{
1312 ClaimSource: &options.ClaimSource{
1319 Name: "X-Auth-Request-Groups",
1320 Values: []options.HeaderValue{
1322 ClaimSource: &options.ClaimSource{
1329 Name: "X-Forwarded-Preferred-Username",
1330 Values: []options.HeaderValue{
1332 ClaimSource: &options.ClaimSource{
1333 Claim: "preferred_username",
1339 Name: "Authorization",
1340 Values: []options.HeaderValue{
1342 ClaimSource: &options.ClaimSource{
1344 BasicAuthPassword: &options.SecretSource{
1345 Value: []byte("This is a secure password"),
1353 err := validation.Validate(pcTest.opts)
1354 assert.NoError(t, err)
1356 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1357 return pcTest.validateUser
1362 pcTest.proxy.provider = &TestProvider{
1363 ProviderData: &providers.ProviderData{},
1367 pcTest.validateUser = true
1369 pcTest.rw = httptest.NewRecorder()
1370 pcTest.req, _ = http.NewRequest(http.MethodGet,
1371 pcTest.opts.ProxyPrefix+authOnlyPath, nil)
1373 created := time.Now()
1374 startSession := &sessions.SessionState{
1375 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1376 err = pcTest.SaveSession(startSession)
1377 assert.NoError(t, err)
1379 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1380 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1381 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1382 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1383 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:This is a secure password"))
1384 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1387func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1388 var pcTest ProcessCookieTest
1390 pcTest.opts = baseTestOptions()
1391 pcTest.opts.InjectResponseHeaders = []options.Header{
1393 Name: "X-Auth-Request-User",
1394 Values: []options.HeaderValue{
1396 ClaimSource: &options.ClaimSource{
1403 Name: "X-Auth-Request-Email",
1404 Values: []options.HeaderValue{
1406 ClaimSource: &options.ClaimSource{
1413 Name: "X-Auth-Request-Groups",
1414 Values: []options.HeaderValue{
1416 ClaimSource: &options.ClaimSource{
1423 Name: "X-Forwarded-Preferred-Username",
1424 Values: []options.HeaderValue{
1426 ClaimSource: &options.ClaimSource{
1427 Claim: "preferred_username",
1433 err := validation.Validate(pcTest.opts)
1434 assert.NoError(t, err)
1436 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1437 return pcTest.validateUser
1442 pcTest.proxy.provider = &TestProvider{
1443 ProviderData: &providers.ProviderData{},
1447 pcTest.validateUser = true
1449 pcTest.rw = httptest.NewRecorder()
1450 pcTest.req, _ = http.NewRequest(http.MethodGet,
1451 pcTest.opts.ProxyPrefix+authOnlyPath, nil)
1453 created := time.Now()
1454 startSession := &sessions.SessionState{
1455 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1456 err = pcTest.SaveSession(startSession)
1457 assert.NoError(t, err)
1459 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1460 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1461 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1462 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1463 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1466func TestAuthSkippedForPreflightRequests(t *testing.T) {
1467 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1469 _, err := w.Write([]byte("response"))
1474 t.Cleanup(upstreamServer.Close)
1476 opts := baseTestOptions()
1477 opts.UpstreamServers = options.UpstreamConfig{
1478 Upstreams: []options.Upstream{
1480 ID: upstreamServer.URL,
1482 URI: upstreamServer.URL,
1486 opts.SkipAuthPreflight = true
1487 err := validation.Validate(opts)
1488 assert.NoError(t, err)
1490 upstreamURL, _ := url.Parse(upstreamServer.URL)
1492 proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
1496 proxy.provider = NewTestProvider(upstreamURL, "")
1497 rw := httptest.NewRecorder()
1498 req, _ := http.NewRequest(http.MethodOptions, "/preflight-request", nil)
1499 proxy.ServeHTTP(rw, req)
1501 assert.Equal(t, 200, rw.Code)
1502 assert.Equal(t, "response", rw.Body.String())
1505type SignatureAuthenticator struct {
1506 auth hmacauth.HmacAuth
1509func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1510 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1514 case hmacauth.ResultNoSignature:
1515 msg = "no signature received"
1516 case hmacauth.ResultMatch:
1517 msg = "signatures match"
1518 case hmacauth.ResultMismatch:
1520 "signatures do not match:\n received: %s\n computed: %s",
1524 panic("unknown result value: " + result.String())
1527 _, err := w.Write([]byte(msg))
1533type SignatureTest struct {
1534 opts *options.Options
1535 upstream *httptest.Server
1537 provider *httptest.Server
1539 rw *httptest.ResponseRecorder
1540 authenticator *SignatureAuthenticator
1541 authProvider providers.Provider
1544func NewSignatureTest() (*SignatureTest, error) {
1545 opts := baseTestOptions()
1546 opts.EmailDomains = []string{"acm.org"}
1548 authenticator := &SignatureAuthenticator{}
1549 upstreamServer := httptest.NewServer(
1550 http.HandlerFunc(authenticator.Authenticate))
1551 upstreamURL, err := url.Parse(upstreamServer.URL)
1555 opts.UpstreamServers = options.UpstreamConfig{
1556 Upstreams: []options.Upstream{
1558 ID: upstreamServer.URL,
1560 URI: upstreamServer.URL,
1565 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1566 _, err := w.Write([]byte(`{"access_token": "my_auth_token"}`))
1571 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1572 providerURL, err := url.Parse(provider.URL)
1576 testProvider := NewTestProvider(providerURL, "mbland@acm.org")
1578 return &SignatureTest{
1584 httptest.NewRecorder(),
1590func (st *SignatureTest) Close() {
1595// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1596// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1598// https://github.com/18F/hmacauth/pull/4
1599type fakeNetConn struct {
1603func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1604 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1605 copy(p, fnc.reqBody)
1607 return bodyLen, io.EOF
1612func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) error {
1613 err := validation.Validate(st.opts)
1617 proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
1621 proxy.provider = st.authProvider
1623 var bodyBuf io.ReadCloser
1625 bodyBuf = io.NopCloser(&fakeNetConn{reqBody: body})
1627 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1628 req.Header = st.header
1630 state := &sessions.SessionState{
1631 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1632 err = proxy.SaveSession(st.rw, req, state)
1636 for _, c := range st.rw.Result().Cookies() {
1639 // This is used by the upstream to validate the signature.
1640 st.authenticator.auth = hmacauth.NewHmacAuth(
1641 crypto.SHA1, []byte(key), upstream.SignatureHeader, upstream.SignatureHeaders)
1642 proxy.ServeHTTP(st.rw, req)
1647func TestRequestSignature(t *testing.T) {
1648 testCases := map[string]struct {
1654 "No request signature": {
1655 method: http.MethodGet,
1658 resp: "no signature received",
1661 method: http.MethodGet,
1663 key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1664 resp: "signatures match",
1667 method: http.MethodPost,
1668 body: `{ "hello": "world!" }`,
1669 key: "d90df39e2d19282840252612dd7c81421a372f61",
1670 resp: "signatures match",
1673 for name, tc := range testCases {
1674 t.Run(name, func(t *testing.T) {
1675 st, err := NewSignatureTest()
1681 st.opts.SignatureKey = fmt.Sprintf("sha1:%s", tc.key)
1683 err = st.MakeRequestWithExpectedKey(tc.method, tc.body, tc.key)
1684 assert.NoError(t, err)
1685 assert.Equal(t, 200, st.rw.Code)
1686 assert.Equal(t, tc.resp, st.rw.Body.String())
1691type ajaxRequestTest struct {
1692 opts *options.Options
1696func newAjaxRequestTest(forceJSONErrors bool) (*ajaxRequestTest, error) {
1697 test := &ajaxRequestTest{}
1698 test.opts = baseTestOptions()
1699 test.opts.ForceJSONErrors = forceJSONErrors
1700 err := validation.Validate(test.opts)
1705 test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
1714func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, []byte, error) {
1715 rw := httptest.NewRecorder()
1716 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1718 return 0, nil, nil, err
1721 test.proxy.ServeHTTP(rw, req)
1722 return rw.Code, rw.Header(), rw.Body.Bytes(), nil
1725func testAjaxUnauthorizedRequest(t *testing.T, header http.Header, forceJSONErrors bool) {
1726 test, err := newAjaxRequestTest(forceJSONErrors)
1732 code, rh, body, err := test.getEndpoint(endpoint, header)
1733 assert.NoError(t, err)
1734 assert.Equal(t, http.StatusUnauthorized, code)
1735 mime := rh.Get("Content-Type")
1736 assert.Equal(t, applicationJSON, mime)
1737 assert.Equal(t, []byte("{}"), body)
1739func TestAjaxUnauthorizedRequest1(t *testing.T) {
1740 header := make(http.Header)
1741 header.Add("accept", applicationJSON)
1743 testAjaxUnauthorizedRequest(t, header, false)
1746func TestAjaxUnauthorizedRequest2(t *testing.T) {
1747 header := make(http.Header)
1748 header.Add("Accept", applicationJSON)
1750 testAjaxUnauthorizedRequest(t, header, false)
1753func TestAjaxUnauthorizedRequestAccept1(t *testing.T) {
1754 header := make(http.Header)
1755 header.Add("Accept", "application/json, text/plain, */*")
1757 testAjaxUnauthorizedRequest(t, header, false)
1760func TestForceJSONErrorsUnauthorizedRequest(t *testing.T) {
1761 testAjaxUnauthorizedRequest(t, nil, true)
1764func TestAjaxForbiddendRequest(t *testing.T) {
1765 test, err := newAjaxRequestTest(false)
1770 header := make(http.Header)
1771 code, rh, _, err := test.getEndpoint(endpoint, header)
1772 assert.NoError(t, err)
1773 assert.Equal(t, http.StatusForbidden, code)
1774 mime := rh.Get("Content-Type")
1775 assert.NotEqual(t, applicationJSON, mime)
1778func TestClearSplitCookie(t *testing.T) {
1779 opts := baseTestOptions()
1780 opts.Cookie.Secret = base64CookieSecret
1781 opts.Cookie.Name = "oauth2"
1782 opts.Cookie.Domains = []string{"abc"}
1783 err := validation.Validate(opts)
1784 assert.NoError(t, err)
1786 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1791 p := OAuthProxy{CookieOptions: &opts.Cookie, sessionStore: store}
1792 var rw = httptest.NewRecorder()
1793 req := httptest.NewRequest("get", "/", nil)
1795 req.AddCookie(&http.Cookie{
1799 req.AddCookie(&http.Cookie{
1803 req.AddCookie(&http.Cookie{
1808 err = p.ClearSessionCookie(rw, req)
1809 assert.NoError(t, err)
1810 header := rw.Header()
1812 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1815func TestClearSingleCookie(t *testing.T) {
1816 opts := baseTestOptions()
1817 opts.Cookie.Name = "oauth2"
1818 opts.Cookie.Domains = []string{"abc"}
1819 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1824 p := OAuthProxy{CookieOptions: &opts.Cookie, sessionStore: store}
1825 var rw = httptest.NewRecorder()
1826 req := httptest.NewRequest("get", "/", nil)
1828 req.AddCookie(&http.Cookie{
1832 req.AddCookie(&http.Cookie{
1837 err = p.ClearSessionCookie(rw, req)
1838 assert.NoError(t, err)
1839 header := rw.Header()
1841 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1844type NoOpKeySet struct {
1847func (NoOpKeySet) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
1848 splitStrings := strings.Split(jwt, ".")
1849 payloadString := splitStrings[1]
1850 return base64.RawURLEncoding.DecodeString(payloadString)
1853func TestGetJwtSession(t *testing.T) {
1856 "sub": "1234567890",
1857 "aud": "https://test.myapp.com",
1859 "email": "john@example.com",
1860 "iss": "https://issuer.example.com",
1865 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1866 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1867 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1868 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1869 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1870 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1872 keyset := NoOpKeySet{}
1873 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1874 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true,
1875 SkipClientIDCheck: true})
1876 verificationOptions := internaloidc.IDTokenVerificationOptions{
1877 AudienceClaims: []string{"aud"},
1878 ClientID: "https://test.myapp.com",
1879 ExtraAudiences: []string{},
1881 internalVerifier := internaloidc.NewVerifier(verifier, verificationOptions)
1883 test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
1884 opts.InjectRequestHeaders = []options.Header{
1886 Name: "Authorization",
1887 Values: []options.HeaderValue{
1889 ClaimSource: &options.ClaimSource{
1897 Name: "X-Forwarded-User",
1898 Values: []options.HeaderValue{
1900 ClaimSource: &options.ClaimSource{
1907 Name: "X-Forwarded-Email",
1908 Values: []options.HeaderValue{
1910 ClaimSource: &options.ClaimSource{
1918 opts.InjectResponseHeaders = []options.Header{
1920 Name: "Authorization",
1921 Values: []options.HeaderValue{
1923 ClaimSource: &options.ClaimSource{
1931 Name: "X-Auth-Request-User",
1932 Values: []options.HeaderValue{
1934 ClaimSource: &options.ClaimSource{
1941 Name: "X-Auth-Request-Email",
1942 Values: []options.HeaderValue{
1944 ClaimSource: &options.ClaimSource{
1951 opts.SkipJwtBearerTokens = true
1952 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), internalVerifier))
1957 tp, _ := test.proxy.provider.(*TestProvider)
1958 tp.GroupValidator = func(s string) bool {
1962 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1963 test.req.Header = map[string][]string{
1964 "Authorization": {authHeader},
1967 test.proxy.ServeHTTP(test.rw, test.req)
1968 if test.rw.Code >= 400 {
1969 t.Fatalf("expected 3xx got %d", test.rw.Code)
1972 // Check PassAuthorization, should overwrite Basic header
1973 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
1974 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "1234567890")
1975 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
1977 // SetAuthorization and SetXAuthRequest
1978 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
1979 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "1234567890")
1980 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
1983func Test_prepareNoCache(t *testing.T) {
1984 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1987 mux := http.NewServeMux()
1988 mux.Handle("/", handler)
1990 rec := httptest.NewRecorder()
1991 req := httptest.NewRequest(http.MethodGet, "/", nil)
1992 mux.ServeHTTP(rec, req)
1994 for k, v := range noCacheHeaders {
1995 assert.Equal(t, rec.Header().Get(k), v)
1999func Test_noCacheHeaders(t *testing.T) {
2000 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2001 _, err := w.Write([]byte("upstream"))
2006 t.Cleanup(upstreamServer.Close)
2008 opts := baseTestOptions()
2009 opts.UpstreamServers = options.UpstreamConfig{
2010 Upstreams: []options.Upstream{
2012 ID: upstreamServer.URL,
2014 URI: upstreamServer.URL,
2018 opts.SkipAuthRegex = []string{".*"}
2019 err := validation.Validate(opts)
2020 assert.NoError(t, err)
2021 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2026 t.Run("not exist in response from upstream", func(t *testing.T) {
2027 rec := httptest.NewRecorder()
2028 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
2029 proxy.ServeHTTP(rec, req)
2031 assert.Equal(t, http.StatusOK, rec.Code)
2032 assert.Equal(t, "upstream", rec.Body.String())
2034 // checking noCacheHeaders does not exists in response headers from upstream
2035 for k := range noCacheHeaders {
2036 assert.Equal(t, "", rec.Header().Get(k))
2040 t.Run("has no-cache", func(t *testing.T) {
2046 path: "/oauth2/sign_in",
2050 path: "/oauth2/sign_out",
2054 path: "/oauth2/start",
2058 path: "/oauth2/callback",
2062 path: "/oauth2/auth",
2066 path: "/oauth2/userinfo",
2075 for _, tt := range tests {
2076 t.Run(tt.path, func(t *testing.T) {
2077 rec := httptest.NewRecorder()
2078 req := httptest.NewRequest(http.MethodGet, tt.path, nil)
2079 proxy.ServeHTTP(rec, req)
2080 cacheControl := rec.Result().Header.Get("Cache-Control")
2081 if tt.hasNoCache != (strings.Contains(cacheControl, "no-cache")) {
2082 t.Errorf(`unexpected "Cache-Control" header: %s`, cacheControl)
2090func TestSignOutCallsBackendLogoutURL(t *testing.T) {
2091 const testIDToken = "test-id-token-12345"
2092 var receivedURL string
2093 backendLogoutServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
2094 receivedURL = req.URL.String()
2095 rw.WriteHeader(http.StatusOK)
2097 defer backendLogoutServer.Close()
2099 opts := baseTestOptions()
2100 opts.Providers[0].BackendLogoutURL = backendLogoutServer.URL + "/logout?id_token_hint={id_token}"
2101 err := validation.Validate(opts)
2102 require.NoError(t, err)
2104 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
2105 require.NoError(t, err)
2107 // Save a session with IDToken so backend logout can use it
2108 rw := httptest.NewRecorder()
2109 req := httptest.NewRequest(http.MethodGet, "/", nil)
2110 err = proxy.sessionStore.Save(rw, req, &sessions.SessionState{
2111 Email: "user@example.com",
2112 IDToken: testIDToken,
2114 require.NoError(t, err)
2115 cookie := rw.Header().Values("Set-Cookie")[0]
2117 // Hit sign_out with the session cookie; backend logout should be called before session is cleared
2118 signOutReq := httptest.NewRequest(http.MethodGet, "/oauth2/sign_out", nil)
2119 signOutReq.Header.Set("Cookie", cookie)
2120 rec := httptest.NewRecorder()
2121 proxy.ServeHTTP(rec, signOutReq)
2123 assert.Equal(t, http.StatusFound, rec.Code, "sign_out should redirect")
2124 assert.Contains(t, receivedURL, "id_token_hint="+testIDToken,
2125 "backend logout URL should have been called with id_token from session")
2128func baseTestOptions() *options.Options {
2129 opts := options.NewOptions()
2130 opts.Cookie.Secret = rawCookieSecret
2131 opts.Providers[0].ID = "providerID"
2132 opts.Providers[0].ClientID = clientID
2133 opts.Providers[0].ClientSecret = clientSecret
2134 opts.EmailDomains = []string{"*"}
2136 // Default injected headers for legacy configuration
2137 opts.InjectRequestHeaders = []options.Header{
2139 Name: "Authorization",
2140 Values: []options.HeaderValue{
2142 ClaimSource: &options.ClaimSource{
2144 BasicAuthPassword: &options.SecretSource{
2145 Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
2152 Name: "X-Forwarded-User",
2153 Values: []options.HeaderValue{
2155 ClaimSource: &options.ClaimSource{
2162 Name: "X-Forwarded-Email",
2163 Values: []options.HeaderValue{
2165 ClaimSource: &options.ClaimSource{
2176func TestTrustedIPs(t *testing.T) {
2181 realClientIPHeader string
2185 // Check unconfigured behavior.
2189 reverseProxy: false,
2190 realClientIPHeader: "X-Real-IP", // Default value
2191 req: func() *http.Request {
2192 req, _ := http.NewRequest(http.MethodGet, "/", nil)
2195 expectTrusted: false,
2197 // Check Unix socket with no trusted IPs configured does not error.
2199 name: "UnixSocketWithoutTrustedIPs",
2201 reverseProxy: false,
2202 realClientIPHeader: "X-Real-IP",
2203 req: func() *http.Request {
2204 req, _ := http.NewRequest(http.MethodGet, "/", nil)
2205 req.RemoteAddr = "@"
2208 expectTrusted: false,
2210 // Check Unix socket with trusted IPs configured returns false (no IP to match).
2212 name: "UnixSocketWithTrustedIPs",
2213 trustedIPs: []string{"127.0.0.1"},
2214 reverseProxy: false,
2215 realClientIPHeader: "X-Real-IP",
2216 req: func() *http.Request {
2217 req, _ := http.NewRequest(http.MethodGet, "/", nil)
2218 req.RemoteAddr = "@"
2221 expectTrusted: false,
2223 // Check using req.RemoteAddr (Options.ReverseProxy == false).
2225 name: "WithRemoteAddr",
2226 trustedIPs: []string{"127.0.0.1"},
2227 reverseProxy: false,
2228 realClientIPHeader: "X-Real-IP", // Default value
2229 req: func() *http.Request {
2230 req, _ := http.NewRequest(http.MethodGet, "/", nil)
2231 req.RemoteAddr = "127.0.0.1:43670"
2234 expectTrusted: true,
2236 // Check ignores req.RemoteAddr match when behind a reverse proxy / missing header.
2238 name: "IgnoresRemoteAddrInReverseProxyMode",
2239 trustedIPs: []string{"127.0.0.1"},
2241 realClientIPHeader: "X-Real-IP", // Default value
2242 req: func() *http.Request {
2243 req, _ := http.NewRequest(http.MethodGet, "/", nil)
2244 req.RemoteAddr = "127.0.0.1:44324"
2247 expectTrusted: false,
2249 // Check successful trusting of localhost in IPv4.
2251 name: "TrustsLocalhostInReverseProxyMode",
2252 trustedIPs: []string{"127.0.0.0/8", "::1"},
2254 realClientIPHeader: "X-Forwarded-For",
2255 req: func() *http.Request {
2256 req, _ := http.NewRequest(http.MethodGet, "/", nil)
2257 req.Header.Add("X-Forwarded-For", "127.0.0.1")
2260 expectTrusted: true,
2262 // Check successful trusting of localhost in IPv6.
2264 name: "TrustsIP6LocalostInReverseProxyMode",
2265 trustedIPs: []string{"127.0.0.0/8", "::1"},
2267 realClientIPHeader: "X-Forwarded-For",
2268 req: func() *http.Request {
2269 req, _ := http.NewRequest(http.MethodGet, "/", nil)
2270 req.Header.Add("X-Forwarded-For", "::1")
2273 expectTrusted: true,
2275 // Check does not trust random IPv4 address.
2277 name: "DoesNotTrustRandomIP4Address",
2278 trustedIPs: []string{"127.0.0.0/8", "::1"},
2280 realClientIPHeader: "X-Forwarded-For",
2281 req: func() *http.Request {
2282 req, _ := http.NewRequest(http.MethodGet, "/", nil)
2283 req.Header.Add("X-Forwarded-For", "12.34.56.78")
2286 expectTrusted: false,
2288 // Check does not trust random IPv6 address.
2290 name: "DoesNotTrustRandomIP6Address",
2291 trustedIPs: []string{"127.0.0.0/8", "::1"},
2293 realClientIPHeader: "X-Forwarded-For",
2294 req: func() *http.Request {
2295 req, _ := http.NewRequest(http.MethodGet, "/", nil)
2296 req.Header.Add("X-Forwarded-For", "::2")
2299 expectTrusted: false,
2301 // Check respects correct header.
2303 name: "RespectsCorrectHeaderInReverseProxyMode",
2304 trustedIPs: []string{"127.0.0.0/8", "::1"},
2306 realClientIPHeader: "X-Forwarded-For",
2307 req: func() *http.Request {
2308 req, _ := http.NewRequest(http.MethodGet, "/", nil)
2309 req.Header.Add("X-Real-IP", "::1")
2312 expectTrusted: false,
2314 // Check doesn't trust if garbage is provided.
2316 name: "DoesNotTrustGarbageInReverseProxyMode",
2317 trustedIPs: []string{"127.0.0.0/8", "::1"},
2319 realClientIPHeader: "X-Forwarded-For",
2320 req: func() *http.Request {
2321 req, _ := http.NewRequest(http.MethodGet, "/", nil)
2322 req.Header.Add("X-Forwarded-For", "adsfljk29242as!!")
2325 expectTrusted: false,
2327 // Check doesn't trust if garbage is provided (no reverse-proxy).
2329 name: "DoesNotTrustGarbage",
2330 trustedIPs: []string{"127.0.0.0/8", "::1"},
2331 reverseProxy: false,
2332 realClientIPHeader: "X-Real-IP",
2333 req: func() *http.Request {
2334 req, _ := http.NewRequest(http.MethodGet, "/", nil)
2335 req.RemoteAddr = "adsfljk29242as!!"
2338 expectTrusted: false,
2342 for _, tt := range tests {
2343 t.Run(tt.name, func(t *testing.T) {
2344 opts := baseTestOptions()
2345 opts.UpstreamServers = options.UpstreamConfig{
2346 Upstreams: []options.Upstream{
2350 Static: ptr.To(true),
2354 opts.TrustedIPs = tt.trustedIPs
2355 opts.ReverseProxy = tt.reverseProxy
2356 opts.RealClientIPHeader = tt.realClientIPHeader
2357 err := validation.Validate(opts)
2358 assert.NoError(t, err)
2360 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
2361 assert.NoError(t, err)
2362 rw := httptest.NewRecorder()
2364 proxy.ServeHTTP(rw, tt.req)
2365 if tt.expectTrusted {
2366 assert.Equal(t, 200, rw.Code)
2368 assert.Equal(t, 403, rw.Code)
2374func Test_buildRoutesAllowlist(t *testing.T) {
2375 type expectedAllowedRoute struct {
2381 testCases := []struct {
2383 skipAuthRegex []string
2384 skipAuthRoutes []string
2385 expectedRoutes []expectedAllowedRoute
2389 name: "No skip auth configured",
2390 skipAuthRegex: []string{},
2391 skipAuthRoutes: []string{},
2392 expectedRoutes: []expectedAllowedRoute{},
2396 name: "Only skipAuthRegex configured",
2397 skipAuthRegex: []string{
2399 "^/baz/[0-9]+/thing",
2401 skipAuthRoutes: []string{},
2402 expectedRoutes: []expectedAllowedRoute{
2406 regexString: "^/foo/bar",
2411 regexString: "^/baz/[0-9]+/thing",
2417 name: "Only skipAuthRoutes configured",
2418 skipAuthRegex: []string{},
2419 skipAuthRoutes: []string{
2421 "POST=^/baz/[0-9]+/thing",
2423 "WEIRD=^/methods/are/allowed",
2424 "PATCH=/second/equals?are=handled&just=fine",
2428 expectedRoutes: []expectedAllowedRoute{
2430 method: http.MethodGet,
2432 regexString: "^/foo/bar",
2435 method: http.MethodPost,
2437 regexString: "^/baz/[0-9]+/thing",
2442 regexString: "^/all/methods$",
2447 regexString: "^/methods/are/allowed",
2452 regexString: "/second/equals?are=handled&just=fine",
2457 regexString: "^/api",
2462 regexString: "^/api",
2468 name: "Both skipAuthRegexes and skipAuthRoutes configured",
2469 skipAuthRegex: []string{
2471 "^/baz/[0-9]+/thing/regex",
2473 skipAuthRoutes: []string{
2475 "POST=^/baz/[0-9]+/thing",
2478 expectedRoutes: []expectedAllowedRoute{
2481 regexString: "^/foo/bar/regex",
2485 regexString: "^/baz/[0-9]+/thing/regex",
2488 method: http.MethodGet,
2489 regexString: "^/foo/bar",
2492 method: http.MethodPost,
2493 regexString: "^/baz/[0-9]+/thing",
2497 regexString: "^/all/methods$",
2503 name: "Invalid skipAuthRegex entry",
2504 skipAuthRegex: []string{
2506 "^/baz/[0-9]+/thing",
2509 skipAuthRoutes: []string{},
2510 expectedRoutes: []expectedAllowedRoute{},
2514 name: "Invalid skipAuthRoutes entry",
2515 skipAuthRegex: []string{},
2516 skipAuthRoutes: []string{
2518 "POST=^/baz/[0-9]+/thing",
2522 expectedRoutes: []expectedAllowedRoute{},
2527 for _, tc := range testCases {
2528 t.Run(tc.name, func(t *testing.T) {
2529 opts := &options.Options{
2530 SkipAuthRegex: tc.skipAuthRegex,
2531 SkipAuthRoutes: tc.skipAuthRoutes,
2533 routes, err := buildRoutesAllowlist(opts)
2535 assert.Error(t, err)
2538 assert.NoError(t, err)
2540 for i, route := range routes {
2541 assert.Greater(t, len(tc.expectedRoutes), i)
2542 assert.Equal(t, route.method, tc.expectedRoutes[i].method)
2543 assert.Equal(t, route.negate, tc.expectedRoutes[i].negate)
2544 assert.Equal(t, route.pathRegex.String(), tc.expectedRoutes[i].regexString)
2550func TestApiRoutes(t *testing.T) {
2552 ajaxAPIServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2554 _, err := w.Write([]byte("AJAX API Request"))
2559 t.Cleanup(ajaxAPIServer.Close)
2561 apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2563 _, err := w.Write([]byte("API Request"))
2568 t.Cleanup(apiServer.Close)
2570 uiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2572 _, err := w.Write([]byte("API Request"))
2577 t.Cleanup(uiServer.Close)
2579 opts := baseTestOptions()
2580 opts.UpstreamServers = options.UpstreamConfig{
2581 Upstreams: []options.Upstream{
2588 ID: ajaxAPIServer.URL,
2590 URI: ajaxAPIServer.URL,
2599 opts.APIRoutes = []string{
2602 opts.SkipProviderButton = true
2603 err := validation.Validate(opts)
2604 assert.NoError(t, err)
2605 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2610 testCases := []struct {
2617 name: "AJAX request matching API regex",
2618 contentType: "application/json",
2619 url: "/api/v1/UserInfo",
2620 shouldRedirect: false,
2623 name: "AJAX request not matching API regex",
2624 contentType: "application/json",
2625 url: "/ajaxapi/v1/UserInfo",
2626 shouldRedirect: false,
2629 name: "Other Request matching API regex",
2630 contentType: "application/grpcwebtext",
2631 url: "/api/v1/UserInfo",
2632 shouldRedirect: false,
2636 contentType: "html",
2637 url: "/ui/index.html",
2638 shouldRedirect: true,
2642 for _, tc := range testCases {
2643 t.Run(tc.name, func(t *testing.T) {
2644 req, err := http.NewRequest(http.MethodGet, tc.url, nil)
2645 req.Header.Set("Accept", tc.contentType)
2646 assert.NoError(t, err)
2648 rw := httptest.NewRecorder()
2649 proxy.ServeHTTP(rw, req)
2651 if tc.shouldRedirect {
2652 assert.Equal(t, 302, rw.Code)
2654 assert.Equal(t, 401, rw.Code)
2660func TestAllowedRequest(t *testing.T) {
2661 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2663 _, err := w.Write([]byte("Allowed Request"))
2668 t.Cleanup(upstreamServer.Close)
2670 opts := baseTestOptions()
2671 opts.UpstreamServers = options.UpstreamConfig{
2672 Upstreams: []options.Upstream{
2674 ID: upstreamServer.URL,
2676 URI: upstreamServer.URL,
2680 opts.SkipAuthRegex = []string{
2681 "^/skip/auth/regex$",
2682 "^/public/.*/endpoint$",
2684 opts.SkipAuthRoutes = []string{
2685 "GET=^/skip/auth/routes/get",
2688 err := validation.Validate(opts)
2689 assert.NoError(t, err)
2690 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2695 testCases := []struct {
2702 name: "Regex GET allowed",
2703 method: http.MethodGet,
2704 url: "/skip/auth/regex",
2708 name: "Regex POST allowed ",
2709 method: http.MethodPost,
2710 url: "/skip/auth/regex",
2714 name: "Regex denied",
2715 method: http.MethodGet,
2716 url: "/wrong/denied",
2720 name: "Regex allowed with fragment-free path",
2721 method: http.MethodGet,
2722 url: "/public/legit/endpoint",
2726 name: "Regex denied when path contains encoded fragment suffix",
2727 method: http.MethodGet,
2728 url: "/public/secret%23/endpoint",
2732 name: "Route allowed",
2733 method: http.MethodGet,
2734 url: "/skip/auth/routes/get",
2738 name: "Route denied with wrong method",
2740 url: "/skip/auth/routes/get",
2744 name: "Route denied with wrong path",
2745 method: http.MethodGet,
2746 url: "/skip/auth/routes/wrong/path",
2750 name: "Route denied with wrong path and method",
2751 method: http.MethodPost,
2752 url: "/skip/auth/routes/wrong/path",
2756 name: "Route allowed with fragment-free path",
2757 method: http.MethodGet,
2758 url: "/foo/public/bar",
2762 name: "Route denied when path contains encoded fragment suffix",
2763 method: http.MethodGet,
2764 url: "/foo/secret%23/bar",
2769 for _, tc := range testCases {
2770 t.Run(tc.name, func(t *testing.T) {
2771 req, err := http.NewRequest(tc.method, tc.url, nil)
2772 assert.NoError(t, err)
2773 assert.Equal(t, tc.allowed, proxy.isAllowedRoute(req))
2775 rw := httptest.NewRecorder()
2776 proxy.ServeHTTP(rw, req)
2779 assert.Equal(t, 200, rw.Code)
2780 assert.Equal(t, "Allowed Request", rw.Body.String())
2782 assert.Equal(t, 403, rw.Code)
2788func TestAllowedRequestWithForwardedUriHeader(t *testing.T) {
2789 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2792 t.Cleanup(upstreamServer.Close)
2794 opts := baseTestOptions()
2795 opts.ReverseProxy = true
2796 opts.UpstreamServers = options.UpstreamConfig{
2797 Upstreams: []options.Upstream{
2799 ID: upstreamServer.URL,
2801 URI: upstreamServer.URL,
2805 opts.SkipAuthRegex = []string{
2806 "^/skip/auth/regex$",
2807 "^/public/.*/endpoint$",
2809 opts.SkipAuthRoutes = []string{
2810 "GET=^/skip/auth/routes/get",
2813 err := validation.Validate(opts)
2814 assert.NoError(t, err)
2815 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2820 testCases := []struct {
2827 name: "Regex GET allowed",
2828 method: http.MethodGet,
2829 url: "/skip/auth/regex",
2833 name: "Regex POST allowed ",
2834 method: http.MethodPost,
2835 url: "/skip/auth/regex",
2839 name: "Regex denied",
2840 method: http.MethodGet,
2841 url: "/wrong/denied",
2845 name: "Regex allowed with fragment-free path",
2846 method: http.MethodGet,
2847 url: "/public/legit/endpoint",
2851 name: "Regex denied when X-Forwarded-Uri contains an encoded fragment suffix",
2852 method: http.MethodGet,
2853 url: "/public/secret%23/endpoint",
2857 name: "Route allowed",
2858 method: http.MethodGet,
2859 url: "/skip/auth/routes/get",
2863 name: "Route denied with wrong method",
2865 url: "/skip/auth/routes/get",
2869 name: "Route denied with wrong path",
2870 method: http.MethodGet,
2871 url: "/skip/auth/routes/wrong/path",
2875 name: "Route denied with wrong path and method",
2876 method: http.MethodPost,
2877 url: "/skip/auth/routes/wrong/path",
2881 name: "Route allowed with fragment-free path",
2882 method: http.MethodGet,
2883 url: "/foo/public/bar",
2887 name: "Route denied when X-Forwarded-Uri contains an encoded fragment suffix",
2888 method: http.MethodGet,
2889 url: "/foo/secret%23/bar",
2894 for _, tc := range testCases {
2895 t.Run(tc.name, func(t *testing.T) {
2896 req, err := http.NewRequest(tc.method, opts.ProxyPrefix+authOnlyPath, nil)
2897 req.Header.Set("X-Forwarded-Uri", tc.url)
2898 req.RemoteAddr = "127.0.0.1:4180"
2899 assert.NoError(t, err)
2901 rw := httptest.NewRecorder()
2902 proxy.ServeHTTP(rw, req)
2905 assert.Equal(t, 202, rw.Code)
2907 assert.Equal(t, 401, rw.Code)
2913func TestAllowedRequestWithForwardedUriHeaderRequiresTrustedProxy(t *testing.T) {
2914 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2917 t.Cleanup(upstreamServer.Close)
2919 opts := baseTestOptions()
2920 opts.ReverseProxy = true
2921 opts.TrustedProxyIPs = []string{"127.0.0.1/32"}
2922 opts.UpstreamServers = options.UpstreamConfig{
2923 Upstreams: []options.Upstream{
2925 ID: upstreamServer.URL,
2927 URI: upstreamServer.URL,
2931 opts.SkipAuthRegex = []string{"^/skip/auth/regex$"}
2933 err := validation.Validate(opts)
2934 assert.NoError(t, err)
2936 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2937 assert.NoError(t, err)
2939 req, err := http.NewRequest(http.MethodGet, opts.ProxyPrefix+authOnlyPath, nil)
2940 assert.NoError(t, err)
2941 req.RemoteAddr = "192.0.2.10:4180"
2942 req.Header.Set("X-Forwarded-Uri", "/skip/auth/regex")
2944 rw := httptest.NewRecorder()
2945 proxy.ServeHTTP(rw, req)
2947 assert.Equal(t, 401, rw.Code)
2950func TestAllowedRequestNegateWithoutMethod(t *testing.T) {
2951 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2953 _, err := w.Write([]byte("Allowed Request"))
2958 t.Cleanup(upstreamServer.Close)
2960 opts := baseTestOptions()
2961 opts.UpstreamServers = options.UpstreamConfig{
2962 Upstreams: []options.Upstream{
2964 ID: upstreamServer.URL,
2966 URI: upstreamServer.URL,
2970 opts.SkipAuthRoutes = []string{
2971 "!=^/api", // any non-api routes
2972 "POST=^/api/public-entity/?$",
2974 err := validation.Validate(opts)
2975 assert.NoError(t, err)
2976 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2981 testCases := []struct {
2988 name: "Some static file allowed",
2989 method: http.MethodGet,
2990 url: "/static/file.txt",
2994 name: "POST to contact form allowed",
2995 method: http.MethodPost,
3000 name: "Regex POST allowed",
3001 method: http.MethodPost,
3002 url: "/api/public-entity",
3006 name: "Regex POST with trailing slash allowed",
3007 method: http.MethodPost,
3008 url: "/api/public-entity/",
3012 name: "Regex GET api route denied",
3013 method: http.MethodGet,
3018 name: "Regex POST api route denied",
3019 method: http.MethodPost,
3024 name: "Regex DELETE api route denied",
3026 url: "/api/users/1",
3031 for _, tc := range testCases {
3032 t.Run(tc.name, func(t *testing.T) {
3033 req, err := http.NewRequest(tc.method, tc.url, nil)
3034 assert.NoError(t, err)
3035 assert.Equal(t, tc.allowed, proxy.isAllowedRoute(req))
3037 rw := httptest.NewRecorder()
3038 proxy.ServeHTTP(rw, req)
3041 assert.Equal(t, 200, rw.Code)
3042 assert.Equal(t, "Allowed Request", rw.Body.String())
3044 assert.Equal(t, 403, rw.Code)
3050func TestAllowedRequestNegateWithMethod(t *testing.T) {
3051 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3053 _, err := w.Write([]byte("Allowed Request"))
3058 t.Cleanup(upstreamServer.Close)
3060 opts := baseTestOptions()
3061 opts.UpstreamServers = options.UpstreamConfig{
3062 Upstreams: []options.Upstream{
3064 ID: upstreamServer.URL,
3066 URI: upstreamServer.URL,
3070 opts.SkipAuthRoutes = []string{
3071 "GET!=^/api", // any non-api routes
3072 "POST=^/api/public-entity/?$",
3074 err := validation.Validate(opts)
3075 assert.NoError(t, err)
3076 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
3081 testCases := []struct {
3088 name: "Some static file allowed",
3089 method: http.MethodGet,
3090 url: "/static/file.txt",
3094 name: "POST to contact form not allowed",
3095 method: http.MethodPost,
3100 name: "Regex POST allowed",
3101 method: http.MethodPost,
3102 url: "/api/public-entity",
3106 name: "Regex POST with trailing slash allowed",
3107 method: http.MethodPost,
3108 url: "/api/public-entity/",
3112 name: "Regex GET api route denied",
3113 method: http.MethodGet,
3118 name: "Regex POST api route denied",
3119 method: http.MethodPost,
3124 name: "Regex DELETE api route denied",
3126 url: "/api/users/1",
3131 for _, tc := range testCases {
3132 t.Run(tc.name, func(t *testing.T) {
3133 req, err := http.NewRequest(tc.method, tc.url, nil)
3134 assert.NoError(t, err)
3135 assert.Equal(t, tc.allowed, proxy.isAllowedRoute(req))
3137 rw := httptest.NewRecorder()
3138 proxy.ServeHTTP(rw, req)
3141 assert.Equal(t, 200, rw.Code)
3142 assert.Equal(t, "Allowed Request", rw.Body.String())
3144 assert.Equal(t, 403, rw.Code)
3150func TestProxyAllowedGroups(t *testing.T) {
3153 allowedGroups []string
3156 expectUnauthorized bool
3159 name: "NoAllowedGroups",
3160 allowedGroups: []string{},
3163 expectUnauthorized: false},
3165 name: "NoAllowedGroupsUserHasGroups",
3166 allowedGroups: []string{},
3167 groups: []string{"a", "b"},
3169 expectUnauthorized: false},
3171 name: "UserInAllowedGroup",
3172 allowedGroups: []string{"a"},
3173 groups: []string{"a", "b"},
3175 expectUnauthorized: false},
3177 name: "UserNotInAllowedGroup",
3178 allowedGroups: []string{"a"},
3179 groups: []string{"c"},
3181 expectUnauthorized: true},
3183 name: "UserInQuerystringGroup",
3184 allowedGroups: []string{"a", "b"},
3185 groups: []string{"a", "c"},
3186 querystring: "?allowed_groups=a",
3187 expectUnauthorized: false},
3189 name: "UserInMultiParamQuerystringGroup",
3190 allowedGroups: []string{"a", "b"},
3191 groups: []string{"b"},
3192 querystring: "?allowed_groups=a&allowed_groups=b,d",
3193 expectUnauthorized: false},
3195 name: "UserInOnlyQuerystringGroup",
3196 allowedGroups: []string{},
3197 groups: []string{"a", "c"},
3198 querystring: "?allowed_groups=a,b",
3199 expectUnauthorized: false},
3201 name: "UserInDelimitedQuerystringGroup",
3202 allowedGroups: []string{"a", "b", "c"},
3203 groups: []string{"c"},
3204 querystring: "?allowed_groups=a,c",
3205 expectUnauthorized: false},
3207 name: "UserNotInQuerystringGroup",
3208 allowedGroups: []string{},
3209 groups: []string{"c"},
3210 querystring: "?allowed_groups=a,b",
3211 expectUnauthorized: true},
3213 name: "UserInConfigGroupNotInQuerystringGroup",
3214 allowedGroups: []string{"a", "b", "c"},
3215 groups: []string{"c"},
3216 querystring: "?allowed_groups=a,b",
3217 expectUnauthorized: true},
3219 name: "UserInQuerystringGroupNotInConfigGroup",
3220 allowedGroups: []string{"a", "b"},
3221 groups: []string{"c"},
3222 querystring: "?allowed_groups=b,c",
3223 expectUnauthorized: true},
3226 for _, tt := range tests {
3227 t.Run(tt.name, func(t *testing.T) {
3228 emailAddress := "test"
3229 created := time.Now()
3231 session := &sessions.SessionState{
3233 Email: emailAddress,
3234 AccessToken: "oauth_token",
3235 CreatedAt: &created,
3238 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3241 t.Cleanup(upstreamServer.Close)
3243 test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
3244 opts.Providers[0].AllowedGroups = tt.allowedGroups
3245 opts.UpstreamServers = options.UpstreamConfig{
3246 Upstreams: []options.Upstream{
3248 ID: upstreamServer.URL,
3250 URI: upstreamServer.URL,
3259 test.req, _ = http.NewRequest(http.MethodGet, fmt.Sprintf("/%s", tt.querystring), nil)
3261 test.req.Header.Add("accept", applicationJSON)
3262 err = test.SaveSession(session)
3263 assert.NoError(t, err)
3264 test.proxy.ServeHTTP(test.rw, test.req)
3266 if tt.expectUnauthorized {
3267 assert.Equal(t, http.StatusForbidden, test.rw.Code)
3269 assert.Equal(t, http.StatusOK, test.rw.Code)
3275func TestAuthOnlyAllowedGroups(t *testing.T) {
3276 testCases := []struct {
3278 allowedGroups []string
3281 expectedStatusCode int
3284 name: "NoAllowedGroups",
3285 allowedGroups: []string{},
3288 expectedStatusCode: http.StatusAccepted,
3291 name: "NoAllowedGroupsUserHasGroups",
3292 allowedGroups: []string{},
3293 groups: []string{"a", "b"},
3295 expectedStatusCode: http.StatusAccepted,
3298 name: "UserInAllowedGroup",
3299 allowedGroups: []string{"a"},
3300 groups: []string{"a", "b"},
3302 expectedStatusCode: http.StatusAccepted,
3305 name: "UserNotInAllowedGroup",
3306 allowedGroups: []string{"a"},
3307 groups: []string{"c"},
3309 expectedStatusCode: http.StatusUnauthorized,
3312 name: "UserInQuerystringGroup",
3313 allowedGroups: []string{"a", "b"},
3314 groups: []string{"a", "c"},
3315 querystring: "?allowed_groups=a",
3316 expectedStatusCode: http.StatusAccepted,
3319 name: "UserInMultiParamQuerystringGroup",
3320 allowedGroups: []string{"a", "b"},
3321 groups: []string{"b"},
3322 querystring: "?allowed_groups=a&allowed_groups=b,d",
3323 expectedStatusCode: http.StatusAccepted,
3326 name: "UserInOnlyQuerystringGroup",
3327 allowedGroups: []string{},
3328 groups: []string{"a", "c"},
3329 querystring: "?allowed_groups=a,b",
3330 expectedStatusCode: http.StatusAccepted,
3333 name: "UserInDelimitedQuerystringGroup",
3334 allowedGroups: []string{"a", "b", "c"},
3335 groups: []string{"c"},
3336 querystring: "?allowed_groups=a,c",
3337 expectedStatusCode: http.StatusAccepted,
3340 name: "UserNotInQuerystringGroup",
3341 allowedGroups: []string{},
3342 groups: []string{"c"},
3343 querystring: "?allowed_groups=a,b",
3344 expectedStatusCode: http.StatusForbidden,
3347 name: "UserInConfigGroupNotInQuerystringGroup",
3348 allowedGroups: []string{"a", "b", "c"},
3349 groups: []string{"c"},
3350 querystring: "?allowed_groups=a,b",
3351 expectedStatusCode: http.StatusForbidden,
3354 name: "UserInQuerystringGroupNotInConfigGroup",
3355 allowedGroups: []string{"a", "b"},
3356 groups: []string{"c"},
3357 querystring: "?allowed_groups=b,c",
3358 expectedStatusCode: http.StatusUnauthorized,
3362 for _, tc := range testCases {
3363 t.Run(tc.name, func(t *testing.T) {
3364 emailAddress := "test"
3365 created := time.Now()
3367 session := &sessions.SessionState{
3369 Email: emailAddress,
3370 AccessToken: "oauth_token",
3371 CreatedAt: &created,
3374 test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {
3375 opts.Providers[0].AllowedGroups = tc.allowedGroups
3381 err = test.SaveSession(session)
3382 assert.NoError(t, err)
3384 test.proxy.ServeHTTP(test.rw, test.req)
3386 assert.Equal(t, tc.expectedStatusCode, test.rw.Code)
3391func TestAuthOnlyAllowedGroupsWithSkipMethods(t *testing.T) {
3392 testCases := []struct {
3398 expectedStatusCode int
3401 name: "UserWithGroupSkipAuthPreflight",
3402 groups: []string{"a", "c"},
3403 method: http.MethodOptions,
3404 ip: "1.2.3.5:43670",
3406 expectedStatusCode: http.StatusAccepted,
3409 name: "UserWithGroupTrustedIp",
3410 groups: []string{"a", "c"},
3411 method: http.MethodGet,
3412 ip: "1.2.3.4:43670",
3414 expectedStatusCode: http.StatusAccepted,
3417 name: "UserWithoutGroupSkipAuthPreflight",
3418 groups: []string{"c"},
3419 method: http.MethodOptions,
3420 ip: "1.2.3.5:43670",
3422 expectedStatusCode: http.StatusForbidden,
3425 name: "UserWithoutGroupTrustedIp",
3426 groups: []string{"c"},
3427 method: http.MethodGet,
3428 ip: "1.2.3.4:43670",
3430 expectedStatusCode: http.StatusForbidden,
3433 name: "UserWithoutSessionSkipAuthPreflight",
3434 method: http.MethodOptions,
3435 ip: "1.2.3.5:43670",
3437 expectedStatusCode: http.StatusAccepted,
3440 name: "UserWithoutSessionTrustedIp",
3441 method: http.MethodGet,
3442 ip: "1.2.3.4:43670",
3444 expectedStatusCode: http.StatusAccepted,
3448 for _, tc := range testCases {
3449 t.Run(tc.name, func(t *testing.T) {
3450 test, err := NewAuthOnlyEndpointTest("?allowed_groups=a,b", func(opts *options.Options) {
3451 opts.SkipAuthPreflight = true
3452 opts.TrustedIPs = []string{"1.2.3.4"}
3458 test.req.Method = tc.method
3459 test.req.RemoteAddr = tc.ip
3462 created := time.Now()
3463 session := &sessions.SessionState{
3466 AccessToken: "oauth_token",
3467 CreatedAt: &created,
3469 err = test.SaveSession(session)
3471 assert.NoError(t, err)
3473 test.proxy.ServeHTTP(test.rw, test.req)
3475 assert.Equal(t, tc.expectedStatusCode, test.rw.Code)
3480func TestAuthOnlyAllowedEmailDomains(t *testing.T) {
3481 testCases := []struct {
3485 expectedStatusCode int
3488 name: "NotEmailRestriction",
3489 email: "toto@example.com",
3491 expectedStatusCode: http.StatusAccepted,
3494 name: "UserInAllowedEmailDomain",
3495 email: "toto@example.com",
3496 querystring: "?allowed_email_domains=example.com",
3497 expectedStatusCode: http.StatusAccepted,
3500 name: "UserNotInAllowedEmailDomain",
3501 email: "toto@example.com",
3502 querystring: "?allowed_email_domains=a.example.com",
3503 expectedStatusCode: http.StatusForbidden,
3506 name: "UserNotInAllowedEmailDomains",
3507 email: "toto@example.com",
3508 querystring: "?allowed_email_domains=a.example.com,b.example.com",
3509 expectedStatusCode: http.StatusForbidden,
3512 name: "UserInAllowedEmailDomains",
3513 email: "toto@example.com",
3514 querystring: "?allowed_email_domains=a.example.com,example.com",
3515 expectedStatusCode: http.StatusAccepted,
3518 name: "UserInAllowedEmailDomainWildcard",
3519 email: "toto@foo.example.com",
3520 querystring: "?allowed_email_domains=*.example.com",
3521 expectedStatusCode: http.StatusAccepted,
3524 name: "UserNotInAllowedEmailDomainWildcard",
3525 email: "toto@example.com",
3526 querystring: "?allowed_email_domains=*.a.example.com",
3527 expectedStatusCode: http.StatusForbidden,
3530 name: "UserInAllowedEmailDomainsWildcard",
3531 email: "toto@example.com",
3532 querystring: "?allowed_email_domains=*.a.example.com,*.b.example.com",
3533 expectedStatusCode: http.StatusForbidden,
3536 name: "UserInAllowedEmailDomainsWildcard",
3537 email: "toto@c.example.com",
3538 querystring: "?allowed_email_domains=a.b.c.example.com,*.c.example.com",
3539 expectedStatusCode: http.StatusAccepted,
3542 name: "UserWithMultipleAtSignsExactDomain",
3543 email: "attacker@evil.com@example.com",
3544 querystring: "?allowed_email_domains=example.com",
3545 expectedStatusCode: http.StatusForbidden,
3548 name: "UserWithMultipleAtSignsWildcardDomain",
3549 email: "attacker@evil.com@foo.example.com",
3550 querystring: "?allowed_email_domains=*.example.com",
3551 expectedStatusCode: http.StatusForbidden,
3554 name: "UserWithMultipleAtSignsDotPrefixedDomain",
3555 email: "attacker@evil.com@foo.example.com",
3556 querystring: "?allowed_email_domains=.example.com",
3557 expectedStatusCode: http.StatusForbidden,
3561 for _, tc := range testCases {
3562 t.Run(tc.name, func(t *testing.T) {
3563 groups := []string{}
3565 created := time.Now()
3567 session := &sessions.SessionState{
3570 AccessToken: "oauth_token",
3571 CreatedAt: &created,
3574 test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {})
3579 err = test.SaveSession(session)
3580 assert.NoError(t, err)
3582 test.proxy.ServeHTTP(test.rw, test.req)
3584 assert.Equal(t, tc.expectedStatusCode, test.rw.Code)
3589func TestStateEncodesCorrectly(t *testing.T) {
3590 state := "some_state_to_test"
3591 nonce := "some_nonce_to_test"
3593 encodedResult := encodeState(nonce, state, true)
3594 assert.Equal(t, "c29tZV9ub25jZV90b190ZXN0OnNvbWVfc3RhdGVfdG9fdGVzdA", encodedResult)
3596 notEncodedResult := encodeState(nonce, state, false)
3597 assert.Equal(t, "some_nonce_to_test:some_state_to_test", notEncodedResult)
3600func TestStateDecodesCorrectly(t *testing.T) {
3601 nonce, redirect, _ := decodeState("c29tZV9ub25jZV90b190ZXN0OnNvbWVfc3RhdGVfdG9fdGVzdA", true)
3603 assert.Equal(t, "some_nonce_to_test", nonce)
3604 assert.Equal(t, "some_state_to_test", redirect)
3606 nonce2, redirect2, _ := decodeState("some_nonce_to_test:some_state_to_test", false)
3608 assert.Equal(t, "some_nonce_to_test", nonce2)
3609 assert.Equal(t, "some_state_to_test", redirect2)
3612func TestAuthOnlyAllowedEmails(t *testing.T) {
3613 testCases := []struct {
3617 expectedStatusCode int
3620 name: "NotEmailRestriction",
3621 email: "toto@example.com",
3623 expectedStatusCode: http.StatusAccepted,
3626 name: "UserInAllowedEmail",
3627 email: "toto@example.com",
3628 querystring: "?allowed_emails=toto@example.com",
3629 expectedStatusCode: http.StatusAccepted,
3632 name: "UserNotInAllowedEmail",
3633 email: "toto@example.com",
3634 querystring: "?allowed_emails=tete@example.com",
3635 expectedStatusCode: http.StatusForbidden,
3638 name: "UserNotInAllowedEmails",
3639 email: "toto@example.com",
3640 querystring: "?allowed_emails=tete@example.com,tutu@example.com",
3641 expectedStatusCode: http.StatusForbidden,
3644 name: "UserInAllowedEmails",
3645 email: "toto@example.com",
3646 querystring: "?allowed_emails=tete@example.com,toto@example.com",
3647 expectedStatusCode: http.StatusAccepted,
3651 for _, tc := range testCases {
3652 t.Run(tc.name, func(t *testing.T) {
3653 groups := []string{}
3655 created := time.Now()
3657 session := &sessions.SessionState{
3660 AccessToken: "oauth_token",
3661 CreatedAt: &created,
3664 test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {})
3669 err = test.SaveSession(session)
3670 assert.NoError(t, err)
3672 test.proxy.ServeHTTP(test.rw, test.req)
3674 assert.Equal(t, tc.expectedStatusCode, test.rw.Code)
3679func TestGetOAuthRedirectURI(t *testing.T) {
3682 setupOpts func(*options.Options) *options.Options
3687 name: "redirect with https schema",
3688 setupOpts: func(baseOpts *options.Options) *options.Options {
3694 Scheme: schemeHTTPS,
3697 want: "https://example/oauth2/callback",
3700 name: "redirect with http schema",
3701 setupOpts: func(baseOpts *options.Options) *options.Options {
3702 baseOpts.Cookie.Secure = false
3711 want: "http://example/oauth2/callback",
3714 name: "relative redirect url",
3715 setupOpts: func(baseOpts *options.Options) *options.Options {
3716 baseOpts.RelativeRedirectURL = true
3719 req: &http.Request{},
3720 want: "/oauth2/callback",
3723 name: "proxy prefix",
3724 setupOpts: func(baseOpts *options.Options) *options.Options {
3725 baseOpts.ProxyPrefix = "/prefix"
3734 want: "https://example/prefix/callback",
3737 name: "proxy prefix with relative redirect",
3738 setupOpts: func(baseOpts *options.Options) *options.Options {
3739 baseOpts.ProxyPrefix = "/prefix"
3740 baseOpts.RelativeRedirectURL = true
3749 want: "/prefix/callback",
3752 for _, tt := range tests {
3753 t.Run(tt.name, func(t *testing.T) {
3754 baseOpts := baseTestOptions()
3755 err := validation.Validate(baseOpts)
3756 assert.NoError(t, err)
3758 proxy, err := NewOAuthProxy(tt.setupOpts(baseOpts), func(string) bool { return true })
3759 assert.NoError(t, err)
3761 assert.Equalf(t, tt.want, proxy.getOAuthRedirectURI(tt.req), "getOAuthRedirectURI(%v)", tt.req)
3766func TestIdTokenPlaceholderInSignOut(t *testing.T) {
3767 opts := baseTestOptions()
3768 opts.WhitelistDomains = []string{"my-oidc-provider.example.com"}
3770 err := validation.Validate(opts)
3771 assert.NoError(t, err)
3773 const emailAddress = "john.doe@example.com"
3774 const userName = "9fcab5c9b889a557"
3775 created := time.Now()
3777 session := &sessions.SessionState{
3779 Groups: []string{"a", "b"},
3780 Email: emailAddress,
3781 IDToken: "eYjjjjjj.vvvv.ddd",
3782 AccessToken: "oauth_token",
3783 CreatedAt: &created,
3786 proxy, err := NewOAuthProxy(opts, func(email string) bool {
3789 assert.NoError(t, err)
3791 // Save the required session
3792 rw := httptest.NewRecorder()
3793 req, _ := http.NewRequest(http.MethodGet, "/", nil)
3794 err = proxy.sessionStore.Save(rw, req, session)
3795 assert.NoError(t, err)
3797 rw = httptest.NewRecorder()
3799 rdUrl := url.QueryEscape("https://my-oidc-provider.example.com/sign_out_page?id_token_hint={id_token}&post_logout_redirect_uri=https://my-app.example.com/")
3800 req, _ = http.NewRequest(http.MethodGet, "/oauth2/sign_out?rd="+rdUrl, nil)
3801 req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{
3802 RequestID: "11111111-2222-4333-8444-555555555555",
3806 proxy.SignOut(rw, req)
3807 newLocation := rw.Header().Values("Location")[0]
3809 assert.Equal(t, "https://my-oidc-provider.example.com/sign_out_page?id_token_hint=eYjjjjjj.vvvv.ddd&post_logout_redirect_uri=https://my-app.example.com/", newLocation)
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