18 "github.com/coreos/go-oidc/v3/oidc"
19 "github.com/mbland/hmacauth"
20 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
21 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
22 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
23 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
24 sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
25 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
26 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
27 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
28 "github.com/stretchr/testify/assert"
32 // The rawCookieSecret is 32 bytes and the base64CookieSecret is the base64
33 // encoded version of this.
34 rawCookieSecret = "secretthirtytwobytes+abcdefghijk"
35 base64CookieSecret = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpams"
36 clientID = "3984n253984d7348dm8234yf982t"
37 clientSecret = "gv3498mfc9t23y23974dm2394dm9"
41 logger.SetFlags(logger.Lshortfile)
44func TestRobotsTxt(t *testing.T) {
45 opts := baseTestOptions()
46 err := validation.Validate(opts)
47 assert.NoError(t, err)
49 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
53 rw := httptest.NewRecorder()
54 req, _ := http.NewRequest("GET", "/robots.txt", nil)
55 proxy.ServeHTTP(rw, req)
56 assert.Equal(t, 200, rw.Code)
57 assert.Equal(t, "User-agent: *\nDisallow: /\n", rw.Body.String())
60type TestProvider struct {
61 *providers.ProviderData
64 GroupValidator func(string) bool
67var _ providers.Provider = (*TestProvider)(nil)
69func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
71 ProviderData: &providers.ProviderData{
72 ProviderName: "Test Provider",
75 Host: providerURL.Host,
76 Path: "/oauth/authorize",
80 Host: providerURL.Host,
85 Host: providerURL.Host,
86 Path: "/api/v1/profile",
88 Scope: "profile.email",
90 EmailAddress: emailAddress,
91 GroupValidator: func(s string) bool {
97func (tp *TestProvider) GetEmailAddress(_ context.Context, _ *sessions.SessionState) (string, error) {
98 return tp.EmailAddress, nil
101func (tp *TestProvider) ValidateSession(_ context.Context, _ *sessions.SessionState) bool {
105func Test_redeemCode(t *testing.T) {
106 opts := baseTestOptions()
107 err := validation.Validate(opts)
108 assert.NoError(t, err)
110 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
115 req := httptest.NewRequest(http.MethodGet, "/", nil)
116 _, err = proxy.redeemCode(req)
117 assert.Equal(t, providers.ErrMissingCode, err)
120func Test_enrichSession(t *testing.T) {
122 sessionUser = "Mr Session"
123 sessionEmail = "session@example.com"
124 providerEmail = "provider@example.com"
127 testCases := map[string]struct {
128 session *sessions.SessionState
132 "Session already has enrichable fields": {
133 session: &sessions.SessionState{
137 expectedUser: sessionUser,
138 expectedEmail: sessionEmail,
140 "Session is missing Email and GetEmailAddress is implemented": {
141 session: &sessions.SessionState{
144 expectedUser: sessionUser,
145 expectedEmail: providerEmail,
147 "Session is missing User and GetUserName is not implemented": {
148 session: &sessions.SessionState{
152 expectedEmail: sessionEmail,
156 for name, tc := range testCases {
157 t.Run(name, func(t *testing.T) {
158 opts := baseTestOptions()
159 err := validation.Validate(opts)
160 assert.NoError(t, err)
162 // intentionally set after validation.Validate(opts) since it will clobber
163 // our TestProvider and call `providers.New` defaulting to `providers.GoogleProvider`
164 opts.SetProvider(NewTestProvider(&url.URL{Host: "www.example.com"}, providerEmail))
165 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
170 err = proxy.enrichSessionState(context.Background(), tc.session)
171 assert.NoError(t, err)
172 assert.Equal(t, tc.expectedUser, tc.session.User)
173 assert.Equal(t, tc.expectedEmail, tc.session.Email)
178func TestBasicAuthPassword(t *testing.T) {
179 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
180 logger.Printf("%#v", r)
184 payload = `{"access_token": "my_auth_token"}`
186 payload = r.Header.Get("Authorization")
188 payload = "No Authorization header found."
192 _, err := w.Write([]byte(payload))
198 basicAuthPassword := "This is a secure password"
199 opts := baseTestOptions()
200 opts.UpstreamServers = options.UpstreamConfig{
201 Upstreams: []options.Upstream{
203 ID: providerServer.URL,
205 URI: providerServer.URL,
210 opts.Cookie.Secure = false
211 opts.InjectRequestHeaders = []options.Header{
213 Name: "Authorization",
214 Values: []options.HeaderValue{
216 ClaimSource: &options.ClaimSource{
218 BasicAuthPassword: &options.SecretSource{
219 Value: []byte(basicAuthPassword),
227 err := validation.Validate(opts)
228 assert.NoError(t, err)
230 providerURL, _ := url.Parse(providerServer.URL)
231 const emailAddress = "john.doe@example.com"
233 opts.SetProvider(NewTestProvider(providerURL, emailAddress))
234 proxy, err := NewOAuthProxy(opts, func(email string) bool {
235 return email == emailAddress
241 // Save the required session
242 rw := httptest.NewRecorder()
243 req, _ := http.NewRequest("GET", "/", nil)
244 err = proxy.sessionStore.Save(rw, req, &sessions.SessionState{
247 assert.NoError(t, err)
249 // Extract the cookie value to inject into the test request
250 cookie := rw.Header().Values("Set-Cookie")[0]
252 req, _ = http.NewRequest("GET", "/", nil)
253 req.Header.Set("Cookie", cookie)
254 rw = httptest.NewRecorder()
255 proxy.ServeHTTP(rw, req)
257 // The username in the basic auth credentials is expected to be equal to the email address from the
258 // auth response, so we use the same variable here.
259 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+basicAuthPassword))
260 assert.Equal(t, expectedHeader, rw.Body.String())
261 providerServer.Close()
264func TestPassGroupsHeadersWithGroups(t *testing.T) {
265 opts := baseTestOptions()
266 opts.InjectRequestHeaders = []options.Header{
268 Name: "X-Forwarded-Groups",
269 Values: []options.HeaderValue{
271 ClaimSource: &options.ClaimSource{
279 err := validation.Validate(opts)
280 assert.NoError(t, err)
282 const emailAddress = "john.doe@example.com"
283 const userName = "9fcab5c9b889a557"
285 groups := []string{"a", "b"}
286 created := time.Now()
287 session := &sessions.SessionState{
291 AccessToken: "oauth_token",
295 proxy, err := NewOAuthProxy(opts, func(email string) bool {
296 return email == emailAddress
298 assert.NoError(t, err)
300 // Save the required session
301 rw := httptest.NewRecorder()
302 req, _ := http.NewRequest("GET", "/", nil)
303 err = proxy.sessionStore.Save(rw, req, session)
304 assert.NoError(t, err)
306 // Extract the cookie value to inject into the test request
307 cookie := rw.Header().Values("Set-Cookie")[0]
309 req, _ = http.NewRequest("GET", "/", nil)
310 req.Header.Set("Cookie", cookie)
311 rw = httptest.NewRecorder()
312 proxy.ServeHTTP(rw, req)
314 assert.Equal(t, []string{"a,b"}, req.Header["X-Forwarded-Groups"])
317type PassAccessTokenTest struct {
318 providerServer *httptest.Server
320 opts *options.Options
323type PassAccessTokenTestOptions struct {
325 ProxyUpstream options.Upstream
328func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTest, error) {
329 patt := &PassAccessTokenTest{}
331 patt.providerServer = httptest.NewServer(
332 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
336 payload = `{"access_token": "my_auth_token"}`
338 payload = r.Header.Get("X-Forwarded-Access-Token")
340 payload = "No access token found."
344 _, err := w.Write([]byte(payload))
350 patt.opts = baseTestOptions()
351 patt.opts.UpstreamServers = options.UpstreamConfig{
352 Upstreams: []options.Upstream{
354 ID: patt.providerServer.URL,
356 URI: patt.providerServer.URL,
360 if opts.ProxyUpstream.ID != "" {
361 patt.opts.UpstreamServers.Upstreams = append(patt.opts.UpstreamServers.Upstreams, opts.ProxyUpstream)
364 patt.opts.Cookie.Secure = false
365 if opts.PassAccessToken {
366 patt.opts.InjectRequestHeaders = []options.Header{
368 Name: "X-Forwarded-Access-Token",
369 Values: []options.HeaderValue{
371 ClaimSource: &options.ClaimSource{
372 Claim: "access_token",
380 err := validation.Validate(patt.opts)
385 providerURL, _ := url.Parse(patt.providerServer.URL)
386 const emailAddress = "michael.bland@gsa.gov"
388 patt.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
389 patt.proxy, err = NewOAuthProxy(patt.opts, func(email string) bool {
390 return email == emailAddress
398func (patTest *PassAccessTokenTest) Close() {
399 patTest.providerServer.Close()
402func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int, cookie string) {
403 rw := httptest.NewRecorder()
405 csrf, err := cookies.NewCSRF(patTest.proxy.CookieOptions)
410 req, err := http.NewRequest(
413 "/oauth2/callback?code=callback_code&state=%s",
414 encodeState(csrf.HashOAuthState(), "%2F"),
416 strings.NewReader(""),
422 // rw is a dummy here, we just want the csrfCookie to add to our req
423 csrfCookie, err := csrf.SetCookie(httptest.NewRecorder(), req)
427 req.AddCookie(csrfCookie)
429 patTest.proxy.ServeHTTP(rw, req)
431 return rw.Code, rw.Header().Values("Set-Cookie")[1]
434// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
435// and cookie and returns body and status code.
436func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
437 cookieName := patTest.proxy.CookieOptions.Name
439 keyPrefix := cookieName + "="
441 for _, field := range strings.Split(cookie, "; ") {
442 value = strings.TrimPrefix(field, keyPrefix)
453 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
457 req.AddCookie(&http.Cookie{
461 Expires: time.Now().Add(time.Duration(24)),
465 rw := httptest.NewRecorder()
466 patTest.proxy.ServeHTTP(rw, req)
467 return rw.Code, rw.Body.String()
470func TestForwardAccessTokenUpstream(t *testing.T) {
471 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
472 PassAccessToken: true,
477 t.Cleanup(patTest.Close)
479 // A successful validation will redirect and set the auth cookie.
480 code, cookie := patTest.getCallbackEndpoint()
482 t.Fatalf("expected 302; got %d", code)
484 assert.NotNil(t, cookie)
486 // Now we make a regular request; the access_token from the cookie is
487 // forwarded as the "X-Forwarded-Access-Token" header. The token is
488 // read by the test provider server and written in the response body.
489 code, payload := patTest.getEndpointWithCookie(cookie, "/")
491 t.Fatalf("expected 200; got %d", code)
493 assert.Equal(t, "my_auth_token", payload)
496func TestStaticProxyUpstream(t *testing.T) {
497 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
498 PassAccessToken: true,
499 ProxyUpstream: options.Upstream{
501 Path: "/static-proxy",
508 t.Cleanup(patTest.Close)
510 // A successful validation will redirect and set the auth cookie.
511 code, cookie := patTest.getCallbackEndpoint()
513 t.Fatalf("expected 302; got %d", code)
515 assert.NotEqual(t, nil, cookie)
517 // Now we make a regular request against the upstream proxy; And validate
518 // the returned status code through the static proxy.
519 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
521 t.Fatalf("expected 200; got %d", code)
523 assert.Equal(t, "Authenticated", payload)
526func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
527 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
528 PassAccessToken: false,
533 t.Cleanup(patTest.Close)
535 // A successful validation will redirect and set the auth cookie.
536 code, cookie := patTest.getCallbackEndpoint()
538 t.Fatalf("expected 302; got %d", code)
540 assert.NotEqual(t, nil, cookie)
542 // Now we make a regular request, but the access token header should
544 code, payload := patTest.getEndpointWithCookie(cookie, "/")
546 t.Fatalf("expected 200; got %d", code)
548 assert.Equal(t, "No access token found.", payload)
551type SignInPageTest struct {
552 opts *options.Options
554 signInRegexp *regexp.Regexp
555 signInProviderRegexp *regexp.Regexp
558const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
559const signInSkipProvider = `>Found<`
561func NewSignInPageTest(skipProvider bool) (*SignInPageTest, error) {
562 var sipTest SignInPageTest
564 sipTest.opts = baseTestOptions()
565 sipTest.opts.SkipProviderButton = skipProvider
566 err := validation.Validate(sipTest.opts)
571 sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
577 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
578 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
583func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
584 rw := httptest.NewRecorder()
585 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
586 sipTest.proxy.ServeHTTP(rw, req)
587 return rw.Code, rw.Body.String()
590type AlwaysSuccessfulValidator struct {
593func (AlwaysSuccessfulValidator) Validate(user, password string) bool {
597func TestManualSignInStoresUserGroupsInTheSession(t *testing.T) {
598 userGroups := []string{"somegroup", "someothergroup"}
600 opts := baseTestOptions()
601 opts.HtpasswdUserGroups = userGroups
602 err := validation.Validate(opts)
607 proxy, err := NewOAuthProxy(opts, func(email string) bool {
613 proxy.basicAuthValidator = AlwaysSuccessfulValidator{}
615 rw := httptest.NewRecorder()
616 formData := url.Values{}
617 formData.Set("username", "someuser")
618 formData.Set("password", "somepass")
619 signInReq, _ := http.NewRequest(http.MethodPost, "/oauth2/sign_in", strings.NewReader(formData.Encode()))
620 signInReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
621 proxy.ServeHTTP(rw, signInReq)
623 assert.Equal(t, http.StatusFound, rw.Code)
625 req, _ := http.NewRequest(http.MethodGet, "/something", strings.NewReader(formData.Encode()))
626 for _, c := range rw.Result().Cookies() {
630 s, err := proxy.sessionStore.Load(req)
634 assert.Equal(t, userGroups, s.Groups)
637func TestSignInPageIncludesTargetRedirect(t *testing.T) {
638 sipTest, err := NewSignInPageTest(false)
642 const endpoint = "/some/random/endpoint"
644 code, body := sipTest.GetEndpoint(endpoint)
645 assert.Equal(t, 403, code)
647 match := sipTest.signInRegexp.FindStringSubmatch(body)
649 t.Fatal("Did not find pattern in body: " +
650 signInRedirectPattern + "\nBody:\n" + body)
652 if match[1] != endpoint {
653 t.Fatal(`expected redirect to "` + endpoint +
654 `", but was "` + match[1] + `"`)
658func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
659 sipTest, err := NewSignInPageTest(false)
663 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
664 assert.Equal(t, 200, code)
666 match := sipTest.signInRegexp.FindStringSubmatch(body)
668 t.Fatal("Did not find pattern in body: " +
669 signInRedirectPattern + "\nBody:\n" + body)
672 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
676func TestSignInPageSkipProvider(t *testing.T) {
677 sipTest, err := NewSignInPageTest(true)
682 endpoint := "/some/random/endpoint"
684 code, body := sipTest.GetEndpoint(endpoint)
685 assert.Equal(t, 302, code)
687 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
689 t.Fatal("Did not find pattern in body: " +
690 signInSkipProvider + "\nBody:\n" + body)
694func TestSignInPageSkipProviderDirect(t *testing.T) {
695 sipTest, err := NewSignInPageTest(true)
700 endpoint := "/sign_in"
702 code, body := sipTest.GetEndpoint(endpoint)
703 assert.Equal(t, 302, code)
705 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
707 t.Fatal("Did not find pattern in body: " +
708 signInSkipProvider + "\nBody:\n" + body)
712type ProcessCookieTest struct {
713 opts *options.Options
715 rw *httptest.ResponseRecorder
720type ProcessCookieTestOpts struct {
721 providerValidateCookieResponse bool
724type OptionsModifier func(*options.Options)
726func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
727 var pcTest ProcessCookieTest
729 pcTest.opts = baseTestOptions()
730 for _, modifier := range modifiers {
731 modifier(pcTest.opts)
733 // First, set the CookieRefresh option so proxy.AesCipher is created,
734 // needed to encrypt the access_token.
735 pcTest.opts.Cookie.Refresh = time.Hour
736 err := validation.Validate(pcTest.opts)
741 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
742 return pcTest.validateUser
747 pcTest.proxy.provider = &TestProvider{
748 ProviderData: &providers.ProviderData{},
749 ValidToken: opts.providerValidateCookieResponse,
751 pcTest.proxy.provider.(*TestProvider).SetAllowedGroups(pcTest.opts.Providers[0].AllowedGroups)
753 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
754 // access_token validation.
755 pcTest.proxy.CookieOptions.Refresh = time.Duration(0)
756 pcTest.rw = httptest.NewRecorder()
757 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
758 pcTest.validateUser = true
762func NewProcessCookieTestWithDefaults() (*ProcessCookieTest, error) {
763 return NewProcessCookieTest(ProcessCookieTestOpts{
764 providerValidateCookieResponse: true,
768func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
769 return NewProcessCookieTest(ProcessCookieTestOpts{
770 providerValidateCookieResponse: true,
774func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
775 err := p.proxy.SaveSession(p.rw, p.req, s)
779 for _, cookie := range p.rw.Result().Cookies() {
780 p.req.AddCookie(cookie)
785func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
786 return p.proxy.LoadCookiedSession(p.req)
789func TestLoadCookiedSession(t *testing.T) {
790 pcTest, err := NewProcessCookieTestWithDefaults()
795 created := time.Now()
796 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
797 err = pcTest.SaveSession(startSession)
798 assert.NoError(t, err)
800 session, err := pcTest.LoadCookiedSession()
804 assert.Equal(t, startSession.Email, session.Email)
805 assert.Equal(t, "", session.User)
806 assert.Equal(t, startSession.AccessToken, session.AccessToken)
809func TestProcessCookieNoCookieError(t *testing.T) {
810 pcTest, err := NewProcessCookieTestWithDefaults()
815 session, err := pcTest.LoadCookiedSession()
816 assert.Error(t, err, "cookie \"_oauth2_proxy\" not present")
818 t.Errorf("expected nil session. got %#v", session)
822func TestProcessCookieRefreshNotSet(t *testing.T) {
823 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
824 opts.Cookie.Expire = time.Duration(23) * time.Hour
830 reference := time.Now().Add(time.Duration(-2) * time.Hour)
832 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
833 err = pcTest.SaveSession(startSession)
834 assert.NoError(t, err)
836 session, err := pcTest.LoadCookiedSession()
837 assert.Equal(t, nil, err)
838 if session.Age() < time.Duration(-2)*time.Hour {
839 t.Errorf("cookie too young %v", session.Age())
841 assert.Equal(t, startSession.Email, session.Email)
844func TestProcessCookieFailIfCookieExpired(t *testing.T) {
845 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
846 opts.Cookie.Expire = time.Duration(24) * time.Hour
852 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
853 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
854 err = pcTest.SaveSession(startSession)
855 assert.NoError(t, err)
857 session, err := pcTest.LoadCookiedSession()
858 assert.NotEqual(t, nil, err)
860 t.Errorf("expected nil session %#v", session)
864func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
865 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
866 opts.Cookie.Expire = time.Duration(24) * time.Hour
872 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
873 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
874 err = pcTest.SaveSession(startSession)
875 assert.NoError(t, err)
877 pcTest.proxy.CookieOptions.Refresh = time.Hour
878 session, err := pcTest.LoadCookiedSession()
879 assert.NotEqual(t, nil, err)
881 t.Errorf("expected nil session %#v", session)
885func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
886 pcTest, err := NewProcessCookieTestWithDefaults()
890 pcTest.req, _ = http.NewRequest("GET",
891 pcTest.opts.ProxyPrefix+"/userinfo", nil)
895func TestUserInfoEndpointAccepted(t *testing.T) {
896 testCases := []struct {
898 session *sessions.SessionState
899 expectedResponse string
902 name: "Full session",
903 session: &sessions.SessionState{
905 Email: "john.doe@example.com",
906 Groups: []string{"example", "groups"},
907 AccessToken: "my_access_token",
909 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n",
912 name: "Minimal session",
913 session: &sessions.SessionState{
915 Email: "john.doe@example.com",
916 Groups: []string{"example", "groups"},
918 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n",
922 session: &sessions.SessionState{
924 Email: "john.doe@example.com",
925 AccessToken: "my_access_token",
927 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\"}\n",
930 name: "With Preferred Username",
931 session: &sessions.SessionState{
933 PreferredUsername: "john",
934 Email: "john.doe@example.com",
935 Groups: []string{"example", "groups"},
936 AccessToken: "my_access_token",
938 expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"],\"preferredUsername\":\"john\"}\n",
942 for _, tc := range testCases {
943 t.Run(tc.name, func(t *testing.T) {
944 test, err := NewUserInfoEndpointTest()
948 err = test.SaveSession(tc.session)
949 assert.NoError(t, err)
951 test.proxy.ServeHTTP(test.rw, test.req)
952 assert.Equal(t, http.StatusOK, test.rw.Code)
953 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
954 assert.Equal(t, tc.expectedResponse, string(bodyBytes))
959func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
960 test, err := NewUserInfoEndpointTest()
965 test.proxy.ServeHTTP(test.rw, test.req)
966 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
969func TestEncodedUrlsStayEncoded(t *testing.T) {
970 encodeTest, err := NewSignInPageTest(false)
974 code, _ := encodeTest.GetEndpoint("/%2F/test1/%2F/test2")
975 assert.Equal(t, 403, code)
978func NewAuthOnlyEndpointTest(querystring string, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
979 pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
983 pcTest.req, _ = http.NewRequest(
985 fmt.Sprintf("%s/auth%s", pcTest.opts.ProxyPrefix, querystring),
990func TestAuthOnlyEndpointAccepted(t *testing.T) {
991 test, err := NewAuthOnlyEndpointTest("")
996 created := time.Now()
997 startSession := &sessions.SessionState{
998 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
999 err = test.SaveSession(startSession)
1000 assert.NoError(t, err)
1002 test.proxy.ServeHTTP(test.rw, test.req)
1003 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1004 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1005 assert.Equal(t, "", string(bodyBytes))
1008func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1009 test, err := NewAuthOnlyEndpointTest("")
1014 test.proxy.ServeHTTP(test.rw, test.req)
1015 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1016 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1017 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1020func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1021 test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
1022 opts.Cookie.Expire = time.Duration(24) * time.Hour
1028 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1029 startSession := &sessions.SessionState{
1030 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1031 err = test.SaveSession(startSession)
1032 assert.NoError(t, err)
1034 test.proxy.ServeHTTP(test.rw, test.req)
1035 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1036 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1037 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1040func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1041 test, err := NewAuthOnlyEndpointTest("")
1046 created := time.Now()
1047 startSession := &sessions.SessionState{
1048 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1049 err = test.SaveSession(startSession)
1050 assert.NoError(t, err)
1051 test.validateUser = false
1053 test.proxy.ServeHTTP(test.rw, test.req)
1054 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1055 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1056 assert.Equal(t, "Unauthorized\n", string(bodyBytes))
1059func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1060 var pcTest ProcessCookieTest
1062 pcTest.opts = baseTestOptions()
1063 pcTest.opts.InjectResponseHeaders = []options.Header{
1065 Name: "X-Auth-Request-User",
1066 Values: []options.HeaderValue{
1068 ClaimSource: &options.ClaimSource{
1075 Name: "X-Auth-Request-Email",
1076 Values: []options.HeaderValue{
1078 ClaimSource: &options.ClaimSource{
1085 Name: "X-Auth-Request-Groups",
1086 Values: []options.HeaderValue{
1088 ClaimSource: &options.ClaimSource{
1095 Name: "X-Forwarded-Preferred-Username",
1096 Values: []options.HeaderValue{
1098 ClaimSource: &options.ClaimSource{
1099 Claim: "preferred_username",
1105 pcTest.opts.Providers[0].AllowedGroups = []string{"oauth_groups"}
1106 err := validation.Validate(pcTest.opts)
1107 assert.NoError(t, err)
1109 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1110 return pcTest.validateUser
1115 pcTest.proxy.provider = &TestProvider{
1116 ProviderData: &providers.ProviderData{},
1120 pcTest.validateUser = true
1122 pcTest.rw = httptest.NewRecorder()
1123 pcTest.req, _ = http.NewRequest("GET",
1124 pcTest.opts.ProxyPrefix+"/auth", nil)
1126 created := time.Now()
1127 startSession := &sessions.SessionState{
1128 User: "oauth_user", Groups: []string{"oauth_groups"}, Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1129 err = pcTest.SaveSession(startSession)
1130 assert.NoError(t, err)
1132 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1133 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1134 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1135 assert.Equal(t, startSession.Groups, pcTest.rw.Header().Values("X-Auth-Request-Groups"))
1136 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1139func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1140 var pcTest ProcessCookieTest
1142 pcTest.opts = baseTestOptions()
1143 pcTest.opts.InjectResponseHeaders = []options.Header{
1145 Name: "X-Auth-Request-User",
1146 Values: []options.HeaderValue{
1148 ClaimSource: &options.ClaimSource{
1155 Name: "X-Auth-Request-Email",
1156 Values: []options.HeaderValue{
1158 ClaimSource: &options.ClaimSource{
1165 Name: "X-Auth-Request-Groups",
1166 Values: []options.HeaderValue{
1168 ClaimSource: &options.ClaimSource{
1175 Name: "X-Forwarded-Preferred-Username",
1176 Values: []options.HeaderValue{
1178 ClaimSource: &options.ClaimSource{
1179 Claim: "preferred_username",
1185 Name: "Authorization",
1186 Values: []options.HeaderValue{
1188 ClaimSource: &options.ClaimSource{
1190 BasicAuthPassword: &options.SecretSource{
1191 Value: []byte("This is a secure password"),
1199 err := validation.Validate(pcTest.opts)
1200 assert.NoError(t, err)
1202 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1203 return pcTest.validateUser
1208 pcTest.proxy.provider = &TestProvider{
1209 ProviderData: &providers.ProviderData{},
1213 pcTest.validateUser = true
1215 pcTest.rw = httptest.NewRecorder()
1216 pcTest.req, _ = http.NewRequest("GET",
1217 pcTest.opts.ProxyPrefix+"/auth", nil)
1219 created := time.Now()
1220 startSession := &sessions.SessionState{
1221 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1222 err = pcTest.SaveSession(startSession)
1223 assert.NoError(t, err)
1225 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1226 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1227 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1228 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1229 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:This is a secure password"))
1230 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1233func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1234 var pcTest ProcessCookieTest
1236 pcTest.opts = baseTestOptions()
1237 pcTest.opts.InjectResponseHeaders = []options.Header{
1239 Name: "X-Auth-Request-User",
1240 Values: []options.HeaderValue{
1242 ClaimSource: &options.ClaimSource{
1249 Name: "X-Auth-Request-Email",
1250 Values: []options.HeaderValue{
1252 ClaimSource: &options.ClaimSource{
1259 Name: "X-Auth-Request-Groups",
1260 Values: []options.HeaderValue{
1262 ClaimSource: &options.ClaimSource{
1269 Name: "X-Forwarded-Preferred-Username",
1270 Values: []options.HeaderValue{
1272 ClaimSource: &options.ClaimSource{
1273 Claim: "preferred_username",
1279 err := validation.Validate(pcTest.opts)
1280 assert.NoError(t, err)
1282 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1283 return pcTest.validateUser
1288 pcTest.proxy.provider = &TestProvider{
1289 ProviderData: &providers.ProviderData{},
1293 pcTest.validateUser = true
1295 pcTest.rw = httptest.NewRecorder()
1296 pcTest.req, _ = http.NewRequest("GET",
1297 pcTest.opts.ProxyPrefix+"/auth", nil)
1299 created := time.Now()
1300 startSession := &sessions.SessionState{
1301 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1302 err = pcTest.SaveSession(startSession)
1303 assert.NoError(t, err)
1305 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1306 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1307 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1308 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1309 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1312func TestAuthSkippedForPreflightRequests(t *testing.T) {
1313 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1315 _, err := w.Write([]byte("response"))
1320 t.Cleanup(upstreamServer.Close)
1322 opts := baseTestOptions()
1323 opts.UpstreamServers = options.UpstreamConfig{
1324 Upstreams: []options.Upstream{
1326 ID: upstreamServer.URL,
1328 URI: upstreamServer.URL,
1332 opts.SkipAuthPreflight = true
1333 err := validation.Validate(opts)
1334 assert.NoError(t, err)
1336 upstreamURL, _ := url.Parse(upstreamServer.URL)
1337 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1339 proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
1343 rw := httptest.NewRecorder()
1344 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1345 proxy.ServeHTTP(rw, req)
1347 assert.Equal(t, 200, rw.Code)
1348 assert.Equal(t, "response", rw.Body.String())
1351type SignatureAuthenticator struct {
1352 auth hmacauth.HmacAuth
1355func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1356 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1360 case hmacauth.ResultNoSignature:
1361 msg = "no signature received"
1362 case hmacauth.ResultMatch:
1363 msg = "signatures match"
1364 case hmacauth.ResultMismatch:
1366 "signatures do not match:\n received: %s\n computed: %s",
1370 panic("unknown result value: " + result.String())
1373 _, err := w.Write([]byte(msg))
1379type SignatureTest struct {
1380 opts *options.Options
1381 upstream *httptest.Server
1383 provider *httptest.Server
1385 rw *httptest.ResponseRecorder
1386 authenticator *SignatureAuthenticator
1389func NewSignatureTest() (*SignatureTest, error) {
1390 opts := baseTestOptions()
1391 opts.EmailDomains = []string{"acm.org"}
1393 authenticator := &SignatureAuthenticator{}
1394 upstreamServer := httptest.NewServer(
1395 http.HandlerFunc(authenticator.Authenticate))
1396 upstreamURL, err := url.Parse(upstreamServer.URL)
1400 opts.UpstreamServers = options.UpstreamConfig{
1401 Upstreams: []options.Upstream{
1403 ID: upstreamServer.URL,
1405 URI: upstreamServer.URL,
1410 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1411 _, err := w.Write([]byte(`{"access_token": "my_auth_token"}`))
1416 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1417 providerURL, err := url.Parse(provider.URL)
1421 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1423 return &SignatureTest{
1429 httptest.NewRecorder(),
1434func (st *SignatureTest) Close() {
1439// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1440// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1441// https://github.com/18F/hmacauth/pull/4
1442type fakeNetConn struct {
1446func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1447 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1448 copy(p, fnc.reqBody)
1450 return bodyLen, io.EOF
1455func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) error {
1456 err := validation.Validate(st.opts)
1460 proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
1465 var bodyBuf io.ReadCloser
1467 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1469 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1470 req.Header = st.header
1472 state := &sessions.SessionState{
1473 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1474 err = proxy.SaveSession(st.rw, req, state)
1478 for _, c := range st.rw.Result().Cookies() {
1481 // This is used by the upstream to validate the signature.
1482 st.authenticator.auth = hmacauth.NewHmacAuth(
1483 crypto.SHA1, []byte(key), upstream.SignatureHeader, upstream.SignatureHeaders)
1484 proxy.ServeHTTP(st.rw, req)
1489func TestRequestSignature(t *testing.T) {
1490 testCases := map[string]struct {
1496 "No request signature": {
1500 resp: "no signature received",
1505 key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1506 resp: "signatures match",
1510 body: `{ "hello": "world!" }`,
1511 key: "d90df39e2d19282840252612dd7c81421a372f61",
1512 resp: "signatures match",
1515 for name, tc := range testCases {
1516 t.Run(name, func(t *testing.T) {
1517 st, err := NewSignatureTest()
1523 st.opts.SignatureKey = fmt.Sprintf("sha1:%s", tc.key)
1525 err = st.MakeRequestWithExpectedKey(tc.method, tc.body, tc.key)
1526 assert.NoError(t, err)
1527 assert.Equal(t, 200, st.rw.Code)
1528 assert.Equal(t, tc.resp, st.rw.Body.String())
1533type ajaxRequestTest struct {
1534 opts *options.Options
1538func newAjaxRequestTest(forceJSONErrors bool) (*ajaxRequestTest, error) {
1539 test := &ajaxRequestTest{}
1540 test.opts = baseTestOptions()
1541 test.opts.ForceJSONErrors = forceJSONErrors
1542 err := validation.Validate(test.opts)
1547 test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
1556func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, []byte, error) {
1557 rw := httptest.NewRecorder()
1558 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1560 return 0, nil, nil, err
1563 test.proxy.ServeHTTP(rw, req)
1564 return rw.Code, rw.Header(), rw.Body.Bytes(), nil
1567func testAjaxUnauthorizedRequest(t *testing.T, header http.Header, forceJSONErrors bool) {
1568 test, err := newAjaxRequestTest(forceJSONErrors)
1574 code, rh, body, err := test.getEndpoint(endpoint, header)
1575 assert.NoError(t, err)
1576 assert.Equal(t, http.StatusUnauthorized, code)
1577 mime := rh.Get("Content-Type")
1578 assert.Equal(t, applicationJSON, mime)
1579 assert.Equal(t, []byte("{}"), body)
1581func TestAjaxUnauthorizedRequest1(t *testing.T) {
1582 header := make(http.Header)
1583 header.Add("accept", applicationJSON)
1585 testAjaxUnauthorizedRequest(t, header, false)
1588func TestAjaxUnauthorizedRequest2(t *testing.T) {
1589 header := make(http.Header)
1590 header.Add("Accept", applicationJSON)
1592 testAjaxUnauthorizedRequest(t, header, false)
1595func TestAjaxUnauthorizedRequestAccept1(t *testing.T) {
1596 header := make(http.Header)
1597 header.Add("Accept", "application/json, text/plain, */*")
1599 testAjaxUnauthorizedRequest(t, header, false)
1602func TestForceJSONErrorsUnauthorizedRequest(t *testing.T) {
1603 testAjaxUnauthorizedRequest(t, nil, true)
1606func TestAjaxForbiddendRequest(t *testing.T) {
1607 test, err := newAjaxRequestTest(false)
1612 header := make(http.Header)
1613 code, rh, _, err := test.getEndpoint(endpoint, header)
1614 assert.NoError(t, err)
1615 assert.Equal(t, http.StatusForbidden, code)
1616 mime := rh.Get("Content-Type")
1617 assert.NotEqual(t, applicationJSON, mime)
1620func TestClearSplitCookie(t *testing.T) {
1621 opts := baseTestOptions()
1622 opts.Cookie.Secret = base64CookieSecret
1623 opts.Cookie.Name = "oauth2"
1624 opts.Cookie.Domains = []string{"abc"}
1625 err := validation.Validate(opts)
1626 assert.NoError(t, err)
1628 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1633 p := OAuthProxy{CookieOptions: &opts.Cookie, sessionStore: store}
1634 var rw = httptest.NewRecorder()
1635 req := httptest.NewRequest("get", "/", nil)
1637 req.AddCookie(&http.Cookie{
1641 req.AddCookie(&http.Cookie{
1645 req.AddCookie(&http.Cookie{
1650 err = p.ClearSessionCookie(rw, req)
1651 assert.NoError(t, err)
1652 header := rw.Header()
1654 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1657func TestClearSingleCookie(t *testing.T) {
1658 opts := baseTestOptions()
1659 opts.Cookie.Name = "oauth2"
1660 opts.Cookie.Domains = []string{"abc"}
1661 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1666 p := OAuthProxy{CookieOptions: &opts.Cookie, sessionStore: store}
1667 var rw = httptest.NewRecorder()
1668 req := httptest.NewRequest("get", "/", nil)
1670 req.AddCookie(&http.Cookie{
1674 req.AddCookie(&http.Cookie{
1679 err = p.ClearSessionCookie(rw, req)
1680 assert.NoError(t, err)
1681 header := rw.Header()
1683 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1686type NoOpKeySet struct {
1689func (NoOpKeySet) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
1690 splitStrings := strings.Split(jwt, ".")
1691 payloadString := splitStrings[1]
1692 return base64.RawURLEncoding.DecodeString(payloadString)
1695func TestGetJwtSession(t *testing.T) {
1698 "sub": "1234567890",
1699 "aud": "https://test.myapp.com",
1701 "email": "john@example.com",
1702 "iss": "https://issuer.example.com",
1707 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1708 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1709 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1710 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1711 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1712 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1714 keyset := NoOpKeySet{}
1715 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1716 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1718 test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) {
1719 opts.InjectRequestHeaders = []options.Header{
1721 Name: "Authorization",
1722 Values: []options.HeaderValue{
1724 ClaimSource: &options.ClaimSource{
1732 Name: "X-Forwarded-User",
1733 Values: []options.HeaderValue{
1735 ClaimSource: &options.ClaimSource{
1742 Name: "X-Forwarded-Email",
1743 Values: []options.HeaderValue{
1745 ClaimSource: &options.ClaimSource{
1753 opts.InjectResponseHeaders = []options.Header{
1755 Name: "Authorization",
1756 Values: []options.HeaderValue{
1758 ClaimSource: &options.ClaimSource{
1766 Name: "X-Auth-Request-User",
1767 Values: []options.HeaderValue{
1769 ClaimSource: &options.ClaimSource{
1776 Name: "X-Auth-Request-Email",
1777 Values: []options.HeaderValue{
1779 ClaimSource: &options.ClaimSource{
1786 opts.SkipJwtBearerTokens = true
1787 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
1792 tp, _ := test.proxy.provider.(*TestProvider)
1793 tp.GroupValidator = func(s string) bool {
1797 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1798 test.req.Header = map[string][]string{
1799 "Authorization": {authHeader},
1802 test.proxy.ServeHTTP(test.rw, test.req)
1803 if test.rw.Code >= 400 {
1804 t.Fatalf("expected 3xx got %d", test.rw.Code)
1807 // Check PassAuthorization, should overwrite Basic header
1808 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
1809 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "1234567890")
1810 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
1812 // SetAuthorization and SetXAuthRequest
1813 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
1814 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "1234567890")
1815 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
1818func Test_prepareNoCache(t *testing.T) {
1819 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1822 mux := http.NewServeMux()
1823 mux.Handle("/", handler)
1825 rec := httptest.NewRecorder()
1826 req := httptest.NewRequest(http.MethodGet, "/", nil)
1827 mux.ServeHTTP(rec, req)
1829 for k, v := range noCacheHeaders {
1830 assert.Equal(t, rec.Header().Get(k), v)
1834func Test_noCacheHeaders(t *testing.T) {
1835 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1836 _, err := w.Write([]byte("upstream"))
1841 t.Cleanup(upstreamServer.Close)
1843 opts := baseTestOptions()
1844 opts.UpstreamServers = options.UpstreamConfig{
1845 Upstreams: []options.Upstream{
1847 ID: upstreamServer.URL,
1849 URI: upstreamServer.URL,
1853 opts.SkipAuthRegex = []string{".*"}
1854 err := validation.Validate(opts)
1855 assert.NoError(t, err)
1856 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
1861 t.Run("not exist in response from upstream", func(t *testing.T) {
1862 rec := httptest.NewRecorder()
1863 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
1864 proxy.ServeHTTP(rec, req)
1866 assert.Equal(t, http.StatusOK, rec.Code)
1867 assert.Equal(t, "upstream", rec.Body.String())
1869 // checking noCacheHeaders does not exists in response headers from upstream
1870 for k := range noCacheHeaders {
1871 assert.Equal(t, "", rec.Header().Get(k))
1875 t.Run("has no-cache", func(t *testing.T) {
1881 path: "/oauth2/sign_in",
1885 path: "/oauth2/sign_out",
1889 path: "/oauth2/start",
1893 path: "/oauth2/callback",
1897 path: "/oauth2/auth",
1901 path: "/oauth2/userinfo",
1910 for _, tt := range tests {
1911 t.Run(tt.path, func(t *testing.T) {
1912 rec := httptest.NewRecorder()
1913 req := httptest.NewRequest(http.MethodGet, tt.path, nil)
1914 proxy.ServeHTTP(rec, req)
1915 cacheControl := rec.Result().Header.Get("Cache-Control")
1916 if tt.hasNoCache != (strings.Contains(cacheControl, "no-cache")) {
1917 t.Errorf(`unexpected "Cache-Control" header: %s`, cacheControl)
1925func baseTestOptions() *options.Options {
1926 opts := options.NewOptions()
1927 opts.Cookie.Secret = rawCookieSecret
1928 opts.Providers[0].ID = "providerID"
1929 opts.Providers[0].ClientID = clientID
1930 opts.Providers[0].ClientSecret = clientSecret
1931 opts.EmailDomains = []string{"*"}
1933 // Default injected headers for legacy configuration
1934 opts.InjectRequestHeaders = []options.Header{
1936 Name: "Authorization",
1937 Values: []options.HeaderValue{
1939 ClaimSource: &options.ClaimSource{
1941 BasicAuthPassword: &options.SecretSource{
1942 Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
1949 Name: "X-Forwarded-User",
1950 Values: []options.HeaderValue{
1952 ClaimSource: &options.ClaimSource{
1959 Name: "X-Forwarded-Email",
1960 Values: []options.HeaderValue{
1962 ClaimSource: &options.ClaimSource{
1973func TestTrustedIPs(t *testing.T) {
1978 realClientIPHeader string
1982 // Check unconfigured behavior.
1986 reverseProxy: false,
1987 realClientIPHeader: "X-Real-IP", // Default value
1988 req: func() *http.Request {
1989 req, _ := http.NewRequest("GET", "/", nil)
1992 expectTrusted: false,
1994 // Check using req.RemoteAddr (Options.ReverseProxy == false).
1996 name: "WithRemoteAddr",
1997 trustedIPs: []string{"127.0.0.1"},
1998 reverseProxy: false,
1999 realClientIPHeader: "X-Real-IP", // Default value
2000 req: func() *http.Request {
2001 req, _ := http.NewRequest("GET", "/", nil)
2002 req.RemoteAddr = "127.0.0.1:43670"
2005 expectTrusted: true,
2007 // Check ignores req.RemoteAddr match when behind a reverse proxy / missing header.
2009 name: "IgnoresRemoteAddrInReverseProxyMode",
2010 trustedIPs: []string{"127.0.0.1"},
2012 realClientIPHeader: "X-Real-IP", // Default value
2013 req: func() *http.Request {
2014 req, _ := http.NewRequest("GET", "/", nil)
2015 req.RemoteAddr = "127.0.0.1:44324"
2018 expectTrusted: false,
2020 // Check successful trusting of localhost in IPv4.
2022 name: "TrustsLocalhostInReverseProxyMode",
2023 trustedIPs: []string{"127.0.0.0/8", "::1"},
2025 realClientIPHeader: "X-Forwarded-For",
2026 req: func() *http.Request {
2027 req, _ := http.NewRequest("GET", "/", nil)
2028 req.Header.Add("X-Forwarded-For", "127.0.0.1")
2031 expectTrusted: true,
2033 // Check successful trusting of localhost in IPv6.
2035 name: "TrustsIP6LocalostInReverseProxyMode",
2036 trustedIPs: []string{"127.0.0.0/8", "::1"},
2038 realClientIPHeader: "X-Forwarded-For",
2039 req: func() *http.Request {
2040 req, _ := http.NewRequest("GET", "/", nil)
2041 req.Header.Add("X-Forwarded-For", "::1")
2044 expectTrusted: true,
2046 // Check does not trust random IPv4 address.
2048 name: "DoesNotTrustRandomIP4Address",
2049 trustedIPs: []string{"127.0.0.0/8", "::1"},
2051 realClientIPHeader: "X-Forwarded-For",
2052 req: func() *http.Request {
2053 req, _ := http.NewRequest("GET", "/", nil)
2054 req.Header.Add("X-Forwarded-For", "12.34.56.78")
2057 expectTrusted: false,
2059 // Check does not trust random IPv6 address.
2061 name: "DoesNotTrustRandomIP6Address",
2062 trustedIPs: []string{"127.0.0.0/8", "::1"},
2064 realClientIPHeader: "X-Forwarded-For",
2065 req: func() *http.Request {
2066 req, _ := http.NewRequest("GET", "/", nil)
2067 req.Header.Add("X-Forwarded-For", "::2")
2070 expectTrusted: false,
2072 // Check respects correct header.
2074 name: "RespectsCorrectHeaderInReverseProxyMode",
2075 trustedIPs: []string{"127.0.0.0/8", "::1"},
2077 realClientIPHeader: "X-Forwarded-For",
2078 req: func() *http.Request {
2079 req, _ := http.NewRequest("GET", "/", nil)
2080 req.Header.Add("X-Real-IP", "::1")
2083 expectTrusted: false,
2085 // Check doesn't trust if garbage is provided.
2087 name: "DoesNotTrustGarbageInReverseProxyMode",
2088 trustedIPs: []string{"127.0.0.0/8", "::1"},
2090 realClientIPHeader: "X-Forwarded-For",
2091 req: func() *http.Request {
2092 req, _ := http.NewRequest("GET", "/", nil)
2093 req.Header.Add("X-Forwarded-For", "adsfljk29242as!!")
2096 expectTrusted: false,
2098 // Check doesn't trust if garbage is provided (no reverse-proxy).
2100 name: "DoesNotTrustGarbage",
2101 trustedIPs: []string{"127.0.0.0/8", "::1"},
2102 reverseProxy: false,
2103 realClientIPHeader: "X-Real-IP",
2104 req: func() *http.Request {
2105 req, _ := http.NewRequest("GET", "/", nil)
2106 req.RemoteAddr = "adsfljk29242as!!"
2109 expectTrusted: false,
2113 for _, tt := range tests {
2114 t.Run(tt.name, func(t *testing.T) {
2115 opts := baseTestOptions()
2116 opts.UpstreamServers = options.UpstreamConfig{
2117 Upstreams: []options.Upstream{
2125 opts.TrustedIPs = tt.trustedIPs
2126 opts.ReverseProxy = tt.reverseProxy
2127 opts.RealClientIPHeader = tt.realClientIPHeader
2128 err := validation.Validate(opts)
2129 assert.NoError(t, err)
2131 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
2132 assert.NoError(t, err)
2133 rw := httptest.NewRecorder()
2135 proxy.ServeHTTP(rw, tt.req)
2136 if tt.expectTrusted {
2137 assert.Equal(t, 200, rw.Code)
2139 assert.Equal(t, 403, rw.Code)
2145func Test_buildRoutesAllowlist(t *testing.T) {
2146 type expectedAllowedRoute struct {
2151 testCases := []struct {
2153 skipAuthRegex []string
2154 skipAuthRoutes []string
2155 expectedRoutes []expectedAllowedRoute
2159 name: "No skip auth configured",
2160 skipAuthRegex: []string{},
2161 skipAuthRoutes: []string{},
2162 expectedRoutes: []expectedAllowedRoute{},
2166 name: "Only skipAuthRegex configured",
2167 skipAuthRegex: []string{
2169 "^/baz/[0-9]+/thing",
2171 skipAuthRoutes: []string{},
2172 expectedRoutes: []expectedAllowedRoute{
2175 regexString: "^/foo/bar",
2179 regexString: "^/baz/[0-9]+/thing",
2185 name: "Only skipAuthRoutes configured",
2186 skipAuthRegex: []string{},
2187 skipAuthRoutes: []string{
2189 "POST=^/baz/[0-9]+/thing",
2191 "WEIRD=^/methods/are/allowed",
2192 "PATCH=/second/equals?are=handled&just=fine",
2194 expectedRoutes: []expectedAllowedRoute{
2197 regexString: "^/foo/bar",
2201 regexString: "^/baz/[0-9]+/thing",
2205 regexString: "^/all/methods$",
2209 regexString: "^/methods/are/allowed",
2213 regexString: "/second/equals?are=handled&just=fine",
2219 name: "Both skipAuthRegexes and skipAuthRoutes configured",
2220 skipAuthRegex: []string{
2222 "^/baz/[0-9]+/thing/regex",
2224 skipAuthRoutes: []string{
2226 "POST=^/baz/[0-9]+/thing",
2229 expectedRoutes: []expectedAllowedRoute{
2232 regexString: "^/foo/bar/regex",
2236 regexString: "^/baz/[0-9]+/thing/regex",
2240 regexString: "^/foo/bar",
2244 regexString: "^/baz/[0-9]+/thing",
2248 regexString: "^/all/methods$",
2254 name: "Invalid skipAuthRegex entry",
2255 skipAuthRegex: []string{
2257 "^/baz/[0-9]+/thing",
2260 skipAuthRoutes: []string{},
2261 expectedRoutes: []expectedAllowedRoute{},
2265 name: "Invalid skipAuthRoutes entry",
2266 skipAuthRegex: []string{},
2267 skipAuthRoutes: []string{
2269 "POST=^/baz/[0-9]+/thing",
2273 expectedRoutes: []expectedAllowedRoute{},
2278 for _, tc := range testCases {
2279 t.Run(tc.name, func(t *testing.T) {
2280 opts := &options.Options{
2281 SkipAuthRegex: tc.skipAuthRegex,
2282 SkipAuthRoutes: tc.skipAuthRoutes,
2284 routes, err := buildRoutesAllowlist(opts)
2286 assert.Error(t, err)
2289 assert.NoError(t, err)
2291 for i, route := range routes {
2292 assert.Greater(t, len(tc.expectedRoutes), i)
2293 assert.Equal(t, route.method, tc.expectedRoutes[i].method)
2294 assert.Equal(t, route.pathRegex.String(), tc.expectedRoutes[i].regexString)
2300func TestAllowedRequest(t *testing.T) {
2301 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2303 _, err := w.Write([]byte("Allowed Request"))
2308 t.Cleanup(upstreamServer.Close)
2310 opts := baseTestOptions()
2311 opts.UpstreamServers = options.UpstreamConfig{
2312 Upstreams: []options.Upstream{
2314 ID: upstreamServer.URL,
2316 URI: upstreamServer.URL,
2320 opts.SkipAuthRegex = []string{
2321 "^/skip/auth/regex$",
2323 opts.SkipAuthRoutes = []string{
2324 "GET=^/skip/auth/routes/get",
2326 err := validation.Validate(opts)
2327 assert.NoError(t, err)
2328 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2333 testCases := []struct {
2340 name: "Regex GET allowed",
2342 url: "/skip/auth/regex",
2346 name: "Regex POST allowed ",
2348 url: "/skip/auth/regex",
2352 name: "Regex denied",
2354 url: "/wrong/denied",
2358 name: "Route allowed",
2360 url: "/skip/auth/routes/get",
2364 name: "Route denied with wrong method",
2366 url: "/skip/auth/routes/get",
2370 name: "Route denied with wrong path",
2372 url: "/skip/auth/routes/wrong/path",
2377 for _, tc := range testCases {
2378 t.Run(tc.name, func(t *testing.T) {
2379 req, err := http.NewRequest(tc.method, tc.url, nil)
2380 assert.NoError(t, err)
2381 assert.Equal(t, tc.allowed, proxy.isAllowedRoute(req))
2383 rw := httptest.NewRecorder()
2384 proxy.ServeHTTP(rw, req)
2387 assert.Equal(t, 200, rw.Code)
2388 assert.Equal(t, "Allowed Request", rw.Body.String())
2390 assert.Equal(t, 403, rw.Code)
2396func TestProxyAllowedGroups(t *testing.T) {
2399 allowedGroups []string
2401 expectUnauthorized bool
2403 {"NoAllowedGroups", []string{}, []string{}, false},
2404 {"NoAllowedGroupsUserHasGroups", []string{}, []string{"a", "b"}, false},
2405 {"UserInAllowedGroup", []string{"a"}, []string{"a", "b"}, false},
2406 {"UserNotInAllowedGroup", []string{"a"}, []string{"c"}, true},
2409 for _, tt := range tests {
2410 t.Run(tt.name, func(t *testing.T) {
2411 emailAddress := "test"
2412 created := time.Now()
2414 session := &sessions.SessionState{
2416 Email: emailAddress,
2417 AccessToken: "oauth_token",
2418 CreatedAt: &created,
2421 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2424 t.Cleanup(upstreamServer.Close)
2426 test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
2427 opts.Providers[0].AllowedGroups = tt.allowedGroups
2428 opts.UpstreamServers = options.UpstreamConfig{
2429 Upstreams: []options.Upstream{
2431 ID: upstreamServer.URL,
2433 URI: upstreamServer.URL,
2442 test.req, _ = http.NewRequest("GET", "/", nil)
2444 test.req.Header.Add("accept", applicationJSON)
2445 err = test.SaveSession(session)
2446 assert.NoError(t, err)
2447 test.proxy.ServeHTTP(test.rw, test.req)
2449 if tt.expectUnauthorized {
2450 assert.Equal(t, http.StatusForbidden, test.rw.Code)
2452 assert.Equal(t, http.StatusOK, test.rw.Code)
2458func TestAuthOnlyAllowedGroups(t *testing.T) {
2459 testCases := []struct {
2461 allowedGroups []string
2464 expectedStatusCode int
2467 name: "NoAllowedGroups",
2468 allowedGroups: []string{},
2471 expectedStatusCode: http.StatusAccepted,
2474 name: "NoAllowedGroupsUserHasGroups",
2475 allowedGroups: []string{},
2476 groups: []string{"a", "b"},
2478 expectedStatusCode: http.StatusAccepted,
2481 name: "UserInAllowedGroup",
2482 allowedGroups: []string{"a"},
2483 groups: []string{"a", "b"},
2485 expectedStatusCode: http.StatusAccepted,
2488 name: "UserNotInAllowedGroup",
2489 allowedGroups: []string{"a"},
2490 groups: []string{"c"},
2492 expectedStatusCode: http.StatusUnauthorized,
2495 name: "UserInQuerystringGroup",
2496 allowedGroups: []string{"a", "b"},
2497 groups: []string{"a", "c"},
2498 querystring: "?allowed_groups=a",
2499 expectedStatusCode: http.StatusAccepted,
2502 name: "UserInMultiParamQuerystringGroup",
2503 allowedGroups: []string{"a", "b"},
2504 groups: []string{"b"},
2505 querystring: "?allowed_groups=a&allowed_groups=b,d",
2506 expectedStatusCode: http.StatusAccepted,
2509 name: "UserInOnlyQuerystringGroup",
2510 allowedGroups: []string{},
2511 groups: []string{"a", "c"},
2512 querystring: "?allowed_groups=a,b",
2513 expectedStatusCode: http.StatusAccepted,
2516 name: "UserInDelimitedQuerystringGroup",
2517 allowedGroups: []string{"a", "b", "c"},
2518 groups: []string{"c"},
2519 querystring: "?allowed_groups=a,c",
2520 expectedStatusCode: http.StatusAccepted,
2523 name: "UserNotInQuerystringGroup",
2524 allowedGroups: []string{},
2525 groups: []string{"c"},
2526 querystring: "?allowed_groups=a,b",
2527 expectedStatusCode: http.StatusForbidden,
2530 name: "UserInConfigGroupNotInQuerystringGroup",
2531 allowedGroups: []string{"a", "b", "c"},
2532 groups: []string{"c"},
2533 querystring: "?allowed_groups=a,b",
2534 expectedStatusCode: http.StatusForbidden,
2537 name: "UserInQuerystringGroupNotInConfigGroup",
2538 allowedGroups: []string{"a", "b"},
2539 groups: []string{"c"},
2540 querystring: "?allowed_groups=b,c",
2541 expectedStatusCode: http.StatusUnauthorized,
2545 for _, tc := range testCases {
2546 t.Run(tc.name, func(t *testing.T) {
2547 emailAddress := "test"
2548 created := time.Now()
2550 session := &sessions.SessionState{
2552 Email: emailAddress,
2553 AccessToken: "oauth_token",
2554 CreatedAt: &created,
2557 test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {
2558 opts.Providers[0].AllowedGroups = tc.allowedGroups
2564 err = test.SaveSession(session)
2565 assert.NoError(t, err)
2567 test.proxy.ServeHTTP(test.rw, test.req)
2569 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