21 "github.com/coreos/go-oidc"
22 "github.com/mbland/hmacauth"
23 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
24 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
25 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
26 "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie"
27 "github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
28 "github.com/oauth2-proxy/oauth2-proxy/providers"
29 "github.com/stretchr/testify/assert"
30 "github.com/stretchr/testify/require"
31 "golang.org/x/net/websocket"
35 // The rawCookieSecret is 32 bytes and the base64CookieSecret is the base64
36 // encoded version of this.
37 rawCookieSecret = "secretthirtytwobytes+abcdefghijk"
38 base64CookieSecret = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpamsK"
42 logger.SetFlags(logger.Lshortfile)
46type WebSocketOrRestHandler struct {
47 restHandler http.Handler
48 wsHandler http.Handler
51func (h *WebSocketOrRestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
52 if r.Header.Get("Upgrade") == "websocket" {
53 h.wsHandler.ServeHTTP(w, r)
55 h.restHandler.ServeHTTP(w, r)
59func TestWebSocketProxy(t *testing.T) {
60 handler := WebSocketOrRestHandler{
61 restHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
63 hostname, _, _ := net.SplitHostPort(r.Host)
64 w.Write([]byte(hostname))
66 wsHandler: websocket.Handler(func(ws *websocket.Conn) {
69 err := websocket.Message.Receive(ws, &data)
71 t.Fatalf("err %s", err)
74 err = websocket.Message.Send(ws, data)
76 t.Fatalf("err %s", err)
80 backend := httptest.NewServer(&handler)
83 backendURL, _ := url.Parse(backend.URL)
85 options := options.NewOptions()
86 var auth hmacauth.HmacAuth
87 options.PassHostHeader = true
88 proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, options, auth)
89 frontend := httptest.NewServer(proxyHandler)
90 defer frontend.Close()
92 frontendURL, _ := url.Parse(frontend.URL)
93 frontendWSURL := "ws://" + frontendURL.Host + "/"
95 ws, err := websocket.Dial(frontendWSURL, "", "http://localhost/")
97 t.Fatalf("err %s", err)
99 request := []byte("hello, world!")
100 err = websocket.Message.Send(ws, request)
102 t.Fatalf("err %s", err)
104 var response = make([]byte, 1024)
105 websocket.Message.Receive(ws, &response)
107 t.Fatalf("err %s", err)
109 if g, e := string(request), string(response); g != e {
110 t.Errorf("got body %q; expected %q", g, e)
113 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
114 res, _ := http.DefaultClient.Do(getReq)
115 bodyBytes, _ := ioutil.ReadAll(res.Body)
116 backendHostname, _, _ := net.SplitHostPort(backendURL.Host)
117 if g, e := string(bodyBytes), backendHostname; g != e {
118 t.Errorf("got body %q; expected %q", g, e)
122func TestNewReverseProxy(t *testing.T) {
123 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
125 hostname, _, _ := net.SplitHostPort(r.Host)
126 w.Write([]byte(hostname))
128 defer backend.Close()
130 backendURL, _ := url.Parse(backend.URL)
131 backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host)
132 backendHost := net.JoinHostPort(backendHostname, backendPort)
133 proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
135 proxyHandler := NewReverseProxy(proxyURL, &options.Options{FlushInterval: time.Second})
136 setProxyUpstreamHostHeader(proxyHandler, proxyURL)
137 frontend := httptest.NewServer(proxyHandler)
138 defer frontend.Close()
140 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
141 res, _ := http.DefaultClient.Do(getReq)
142 bodyBytes, _ := ioutil.ReadAll(res.Body)
143 if g, e := string(bodyBytes), backendHostname; g != e {
144 t.Errorf("got body %q; expected %q", g, e)
148func TestEncodedSlashes(t *testing.T) {
150 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
154 defer backend.Close()
156 b, _ := url.Parse(backend.URL)
157 proxyHandler := NewReverseProxy(b, &options.Options{FlushInterval: time.Second})
158 setProxyDirector(proxyHandler)
159 frontend := httptest.NewServer(proxyHandler)
160 defer frontend.Close()
162 f, _ := url.Parse(frontend.URL)
163 encodedPath := "/a%2Fb/?c=1"
164 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: f.Host, Opaque: encodedPath}}
165 _, err := http.DefaultClient.Do(getReq)
167 t.Fatalf("err %s", err)
169 if seen != encodedPath {
170 t.Errorf("got bad request %q expected %q", seen, encodedPath)
174func TestRobotsTxt(t *testing.T) {
175 opts := options.NewOptions()
176 opts.ClientID = "asdlkjx"
177 opts.ClientSecret = "alkgks"
178 opts.Cookie.Secret = rawCookieSecret
179 validation.Validate(opts)
181 proxy := NewOAuthProxy(opts, func(string) bool { return true })
182 rw := httptest.NewRecorder()
183 req, _ := http.NewRequest("GET", "/robots.txt", nil)
184 proxy.ServeHTTP(rw, req)
185 assert.Equal(t, 200, rw.Code)
186 assert.Equal(t, "User-agent: *\nDisallow: /", rw.Body.String())
189func TestIsValidRedirect(t *testing.T) {
190 opts := options.NewOptions()
191 opts.ClientID = "skdlfj"
192 opts.ClientSecret = "fgkdsgj"
193 opts.Cookie.Secret = base64CookieSecret
194 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
195 opts.WhitelistDomains = []string{
199 ".sub.port.bar:8080",
201 ".sub.anyport.bar:*",
203 validation.Validate(opts)
205 proxy := NewOAuthProxy(opts, func(string) bool { return true })
207 testCases := []struct {
208 Desc, Redirect string
214 ExpectedResult: false,
218 Redirect: "/redirect",
219 ExpectedResult: true,
223 Redirect: "//redirect",
224 ExpectedResult: false,
228 Redirect: "http://foo.bar/redirect",
229 ExpectedResult: true,
233 Redirect: "https://foo.bar/redirect",
234 ExpectedResult: true,
237 Desc: "invalidHTTPSubdomain",
238 Redirect: "http://baz.foo.bar/redirect",
239 ExpectedResult: false,
242 Desc: "invalidHTTPSSubdomain",
243 Redirect: "https://baz.foo.bar/redirect",
244 ExpectedResult: false,
247 Desc: "validHTTPSubdomain",
248 Redirect: "http://baz.bar.foo/redirect",
249 ExpectedResult: true,
252 Desc: "validHTTPSSubdomain",
253 Redirect: "https://baz.bar.foo/redirect",
254 ExpectedResult: true,
257 Desc: "validHTTPDomain",
258 Redirect: "http://bar.foo/redirect",
259 ExpectedResult: true,
262 Desc: "invalidHTTP1",
263 Redirect: "http://foo.bar.evil.corp/redirect",
264 ExpectedResult: false,
267 Desc: "invalidHTTPS1",
268 Redirect: "https://foo.bar.evil.corp/redirect",
269 ExpectedResult: false,
272 Desc: "invalidHTTP2",
273 Redirect: "http://evil.corp/redirect?rd=foo.bar",
274 ExpectedResult: false,
277 Desc: "invalidHTTPS2",
278 Redirect: "https://evil.corp/redirect?rd=foo.bar",
279 ExpectedResult: false,
283 Redirect: "https://evil.corp:3838/redirect",
284 ExpectedResult: false,
287 Desc: "invalidEmptyPort",
288 Redirect: "http://foo.bar:3838/redirect",
289 ExpectedResult: false,
292 Desc: "invalidEmptyPortSubdomain",
293 Redirect: "http://baz.bar.foo:3838/redirect",
294 ExpectedResult: false,
297 Desc: "validSpecificPort",
298 Redirect: "http://port.bar:8080/redirect",
299 ExpectedResult: true,
302 Desc: "invalidSpecificPort",
303 Redirect: "http://port.bar:3838/redirect",
304 ExpectedResult: false,
307 Desc: "validSpecificPortSubdomain",
308 Redirect: "http://foo.sub.port.bar:8080/redirect",
309 ExpectedResult: true,
312 Desc: "invalidSpecificPortSubdomain",
313 Redirect: "http://foo.sub.port.bar:3838/redirect",
314 ExpectedResult: false,
317 Desc: "validAnyPort1",
318 Redirect: "http://anyport.bar:8080/redirect",
319 ExpectedResult: true,
322 Desc: "validAnyPort2",
323 Redirect: "http://anyport.bar:8081/redirect",
324 ExpectedResult: true,
327 Desc: "validAnyPortSubdomain1",
328 Redirect: "http://a.sub.anyport.bar:8080/redirect",
329 ExpectedResult: true,
332 Desc: "validAnyPortSubdomain2",
333 Redirect: "http://a.sub.anyport.bar:8081/redirect",
334 ExpectedResult: true,
337 Desc: "openRedirect1",
338 Redirect: "/\\evil.com",
339 ExpectedResult: false,
342 Desc: "openRedirectSpace1",
343 Redirect: "/ /evil.com",
344 ExpectedResult: false,
347 Desc: "openRedirectSpace2",
348 Redirect: "/ \\evil.com",
349 ExpectedResult: false,
352 Desc: "openRedirectTab1",
353 Redirect: "/\t/evil.com",
354 ExpectedResult: false,
357 Desc: "openRedirectTab2",
358 Redirect: "/\t\\evil.com",
359 ExpectedResult: false,
362 Desc: "openRedirectVerticalTab1",
363 Redirect: "/\v/evil.com",
364 ExpectedResult: false,
367 Desc: "openRedirectVerticalTab2",
368 Redirect: "/\v\\evil.com",
369 ExpectedResult: false,
372 Desc: "openRedirectNewLine1",
373 Redirect: "/\n/evil.com",
374 ExpectedResult: false,
377 Desc: "openRedirectNewLine2",
378 Redirect: "/\n\\evil.com",
379 ExpectedResult: false,
382 Desc: "openRedirectCarriageReturn1",
383 Redirect: "/\r/evil.com",
384 ExpectedResult: false,
387 Desc: "openRedirectCarriageReturn2",
388 Redirect: "/\r\\evil.com",
389 ExpectedResult: false,
392 Desc: "openRedirectTripleTab",
393 Redirect: "/\t\t/\t/evil.com",
394 ExpectedResult: false,
397 Desc: "openRedirectTripleTab2",
398 Redirect: "/\t\t\\\t/evil.com",
399 ExpectedResult: false,
402 Desc: "openRedirectQuadTab1",
403 Redirect: "/\t\t/\t\t\\evil.com",
404 ExpectedResult: false,
407 Desc: "openRedirectQuadTab2",
408 Redirect: "/\t\t\\\t\t/evil.com",
409 ExpectedResult: false,
412 Desc: "openRedirectPeriod1",
413 Redirect: "/./\\evil.com",
414 ExpectedResult: false,
417 Desc: "openRedirectPeriod2",
418 Redirect: "/./../../\\evil.com",
419 ExpectedResult: false,
422 Desc: "openRedirectDoubleTab",
423 Redirect: "/\t/\t\\evil.com",
424 ExpectedResult: false,
428 for _, tc := range testCases {
429 t.Run(tc.Desc, func(t *testing.T) {
430 result := proxy.IsValidRedirect(tc.Redirect)
432 if result != tc.ExpectedResult {
433 t.Errorf("expected %t got %t", tc.ExpectedResult, result)
439func TestOpenRedirects(t *testing.T) {
440 opts := options.NewOptions()
441 opts.ClientID = "skdlfj"
442 opts.ClientSecret = "fgkdsgj"
443 opts.Cookie.Secret = rawCookieSecret
444 opts.EmailDomains = []string{"*"}
445 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
446 opts.WhitelistDomains = []string{
450 ".sub.port.bar:8080",
452 ".sub.anyport.bar:*",
453 "www.whitelisteddomain.tld",
455 err := validation.Validate(opts)
460 proxy := NewOAuthProxy(opts, func(string) bool { return true })
462 file, err := os.Open("./test/openredirects.txt")
468 scanner := bufio.NewScanner(file)
471 t.Run(rd, func(t *testing.T) {
472 rdUnescaped, err := url.QueryUnescape(rd)
476 if proxy.IsValidRedirect(rdUnescaped) {
477 t.Errorf("Expected %q to not be valid (unescaped: %q)", rd, rdUnescaped)
482 if err := scanner.Err(); err != nil {
487type TestProvider struct {
488 *providers.ProviderData
491 GroupValidator func(string) bool
494var _ providers.Provider = (*TestProvider)(nil)
496func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
497 return &TestProvider{
498 ProviderData: &providers.ProviderData{
499 ProviderName: "Test Provider",
502 Host: providerURL.Host,
503 Path: "/oauth/authorize",
507 Host: providerURL.Host,
508 Path: "/oauth/token",
510 ProfileURL: &url.URL{
512 Host: providerURL.Host,
513 Path: "/api/v1/profile",
515 Scope: "profile.email",
517 EmailAddress: emailAddress,
518 GroupValidator: func(s string) bool {
524func (tp *TestProvider) GetEmailAddress(ctx context.Context, session *sessions.SessionState) (string, error) {
525 return tp.EmailAddress, nil
528func (tp *TestProvider) ValidateSessionState(ctx context.Context, session *sessions.SessionState) bool {
532func TestBasicAuthPassword(t *testing.T) {
533 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
534 logger.Printf("%#v", r)
538 payload = `{"access_token": "my_auth_token"}`
540 payload = r.Header.Get("Authorization")
542 payload = "No Authorization header found."
546 w.Write([]byte(payload))
548 opts := options.NewOptions()
549 opts.Upstreams = append(opts.Upstreams, providerServer.URL)
550 // The CookieSecret must be 32 bytes in order to create the AES
552 opts.Cookie.Secret = "xyzzyplughxyzzyplughxyzzyplughxp"
553 opts.ClientID = "dlgkj"
554 opts.ClientSecret = "alkgret"
555 opts.Cookie.Secure = false
556 opts.PassBasicAuth = true
557 opts.SetBasicAuth = true
558 opts.PassUserHeaders = true
559 opts.PreferEmailToUser = true
560 opts.BasicAuthPassword = "This is a secure password"
561 validation.Validate(opts)
563 providerURL, _ := url.Parse(providerServer.URL)
564 const emailAddress = "john.doe@example.com"
566 opts.SetProvider(NewTestProvider(providerURL, emailAddress))
567 proxy := NewOAuthProxy(opts, func(email string) bool {
568 return email == emailAddress
571 rw := httptest.NewRecorder()
572 req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:", strings.NewReader(""))
573 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
574 proxy.ServeHTTP(rw, req)
576 t.Fatalf("expected 3xx got %d", rw.Code)
578 cookie := rw.Header().Values("Set-Cookie")[1]
580 cookieName := proxy.CookieName
582 keyPrefix := cookieName + "="
584 for _, field := range strings.Split(cookie, "; ") {
585 value = strings.TrimPrefix(field, keyPrefix)
593 req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
594 req.AddCookie(&http.Cookie{
598 Expires: time.Now().Add(time.Duration(24)),
601 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
603 rw = httptest.NewRecorder()
604 proxy.ServeHTTP(rw, req)
606 // The username in the basic auth credentials is expected to be equal to the email address from the
607 // auth response, so we use the same variable here.
608 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
609 assert.Equal(t, expectedHeader, rw.Body.String())
610 providerServer.Close()
613func TestBasicAuthWithEmail(t *testing.T) {
614 opts := options.NewOptions()
615 opts.PassBasicAuth = true
616 opts.PassUserHeaders = false
617 opts.PreferEmailToUser = false
618 opts.BasicAuthPassword = "This is a secure password"
619 validation.Validate(opts)
621 const emailAddress = "john.doe@example.com"
622 const userName = "9fcab5c9b889a557"
624 // The username in the basic auth credentials is expected to be equal to the email address from the
625 expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
626 expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
628 created := time.Now()
629 session := &sessions.SessionState{
632 AccessToken: "oauth_token",
636 rw := httptest.NewRecorder()
637 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
638 proxy := NewOAuthProxy(opts, func(email string) bool {
639 return email == emailAddress
641 proxy.addHeadersForProxying(rw, req, session)
642 assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
643 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
646 opts.PreferEmailToUser = true
648 rw := httptest.NewRecorder()
649 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
651 proxy := NewOAuthProxy(opts, func(email string) bool {
652 return email == emailAddress
654 proxy.addHeadersForProxying(rw, req, session)
655 assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
656 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
660func TestPassUserHeadersWithEmail(t *testing.T) {
661 opts := options.NewOptions()
662 opts.PassBasicAuth = false
663 opts.PassUserHeaders = true
664 opts.PreferEmailToUser = false
665 validation.Validate(opts)
667 const emailAddress = "john.doe@example.com"
668 const userName = "9fcab5c9b889a557"
670 created := time.Now()
671 session := &sessions.SessionState{
674 AccessToken: "oauth_token",
678 rw := httptest.NewRecorder()
679 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
680 proxy := NewOAuthProxy(opts, func(email string) bool {
681 return email == emailAddress
683 proxy.addHeadersForProxying(rw, req, session)
684 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
687 opts.PreferEmailToUser = true
689 rw := httptest.NewRecorder()
690 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
692 proxy := NewOAuthProxy(opts, func(email string) bool {
693 return email == emailAddress
695 proxy.addHeadersForProxying(rw, req, session)
696 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
700type PassAccessTokenTest struct {
701 providerServer *httptest.Server
703 opts *options.Options
706type PassAccessTokenTestOptions struct {
711func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
712 t := &PassAccessTokenTest{}
714 t.providerServer = httptest.NewServer(
715 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
719 payload = `{"access_token": "my_auth_token"}`
721 payload = r.Header.Get("X-Forwarded-Access-Token")
723 payload = "No access token found."
727 w.Write([]byte(payload))
730 t.opts = options.NewOptions()
731 t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL)
732 if opts.ProxyUpstream != "" {
733 t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream)
735 // The CookieSecret must be 32 bytes in order to create the AES
737 t.opts.Cookie.Secret = "xyzzyplughxyzzyplughxyzzyplughxp"
738 t.opts.ClientID = "slgkj"
739 t.opts.ClientSecret = "gfjgojl"
740 t.opts.Cookie.Secure = false
741 t.opts.PassAccessToken = opts.PassAccessToken
742 validation.Validate(t.opts)
744 providerURL, _ := url.Parse(t.providerServer.URL)
745 const emailAddress = "michael.bland@gsa.gov"
747 t.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
748 t.proxy = NewOAuthProxy(t.opts, func(email string) bool {
749 return email == emailAddress
754func (patTest *PassAccessTokenTest) Close() {
755 patTest.providerServer.Close()
758func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
760 rw := httptest.NewRecorder()
761 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
762 strings.NewReader(""))
766 req.AddCookie(patTest.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
767 patTest.proxy.ServeHTTP(rw, req)
768 return rw.Code, rw.Header().Values("Set-Cookie")[1]
771// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
772// and cookie and returns body and status code.
773func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
774 cookieName := patTest.proxy.CookieName
776 keyPrefix := cookieName + "="
778 for _, field := range strings.Split(cookie, "; ") {
779 value = strings.TrimPrefix(field, keyPrefix)
790 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
794 req.AddCookie(&http.Cookie{
798 Expires: time.Now().Add(time.Duration(24)),
802 rw := httptest.NewRecorder()
803 patTest.proxy.ServeHTTP(rw, req)
804 return rw.Code, rw.Body.String()
807func TestForwardAccessTokenUpstream(t *testing.T) {
808 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
809 PassAccessToken: true,
811 defer patTest.Close()
813 // A successful validation will redirect and set the auth cookie.
814 code, cookie := patTest.getCallbackEndpoint()
816 t.Fatalf("expected 302; got %d", code)
818 assert.NotEqual(t, nil, cookie)
820 // Now we make a regular request; the access_token from the cookie is
821 // forwarded as the "X-Forwarded-Access-Token" header. The token is
822 // read by the test provider server and written in the response body.
823 code, payload := patTest.getEndpointWithCookie(cookie, "/")
825 t.Fatalf("expected 200; got %d", code)
827 assert.Equal(t, "my_auth_token", payload)
830func TestStaticProxyUpstream(t *testing.T) {
831 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
832 PassAccessToken: true,
833 ProxyUpstream: "static://200/static-proxy",
836 defer patTest.Close()
838 // A successful validation will redirect and set the auth cookie.
839 code, cookie := patTest.getCallbackEndpoint()
841 t.Fatalf("expected 302; got %d", code)
843 assert.NotEqual(t, nil, cookie)
845 // Now we make a regular request against the upstream proxy; And validate
846 // the returned status code through the static proxy.
847 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
849 t.Fatalf("expected 200; got %d", code)
851 assert.Equal(t, "Authenticated", payload)
854func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
855 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
856 PassAccessToken: false,
858 defer patTest.Close()
860 // A successful validation will redirect and set the auth cookie.
861 code, cookie := patTest.getCallbackEndpoint()
863 t.Fatalf("expected 302; got %d", code)
865 assert.NotEqual(t, nil, cookie)
867 // Now we make a regular request, but the access token header should
869 code, payload := patTest.getEndpointWithCookie(cookie, "/")
871 t.Fatalf("expected 200; got %d", code)
873 assert.Equal(t, "No access token found.", payload)
876type SignInPageTest struct {
877 opts *options.Options
879 signInRegexp *regexp.Regexp
880 signInProviderRegexp *regexp.Regexp
883const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
884const signInSkipProvider = `>Found<`
886func NewSignInPageTest(skipProvider bool) *SignInPageTest {
887 var sipTest SignInPageTest
889 sipTest.opts = options.NewOptions()
890 sipTest.opts.Cookie.Secret = rawCookieSecret
891 sipTest.opts.ClientID = "lkdgj"
892 sipTest.opts.ClientSecret = "sgiufgoi"
893 sipTest.opts.SkipProviderButton = skipProvider
894 validation.Validate(sipTest.opts)
896 sipTest.proxy = NewOAuthProxy(sipTest.opts, func(email string) bool {
899 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
900 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
905func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
906 rw := httptest.NewRecorder()
907 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
908 sipTest.proxy.ServeHTTP(rw, req)
909 return rw.Code, rw.Body.String()
912func TestSignInPageIncludesTargetRedirect(t *testing.T) {
913 sipTest := NewSignInPageTest(false)
914 const endpoint = "/some/random/endpoint"
916 code, body := sipTest.GetEndpoint(endpoint)
917 assert.Equal(t, 403, code)
919 match := sipTest.signInRegexp.FindStringSubmatch(body)
921 t.Fatal("Did not find pattern in body: " +
922 signInRedirectPattern + "\nBody:\n" + body)
924 if match[1] != endpoint {
925 t.Fatal(`expected redirect to "` + endpoint +
926 `", but was "` + match[1] + `"`)
930func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
931 sipTest := NewSignInPageTest(false)
932 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
933 assert.Equal(t, 200, code)
935 match := sipTest.signInRegexp.FindStringSubmatch(body)
937 t.Fatal("Did not find pattern in body: " +
938 signInRedirectPattern + "\nBody:\n" + body)
941 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
945func TestSignInPageSkipProvider(t *testing.T) {
946 sipTest := NewSignInPageTest(true)
947 const endpoint = "/some/random/endpoint"
949 code, body := sipTest.GetEndpoint(endpoint)
950 assert.Equal(t, 302, code)
952 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
954 t.Fatal("Did not find pattern in body: " +
955 signInSkipProvider + "\nBody:\n" + body)
959func TestSignInPageSkipProviderDirect(t *testing.T) {
960 sipTest := NewSignInPageTest(true)
961 const endpoint = "/sign_in"
963 code, body := sipTest.GetEndpoint(endpoint)
964 assert.Equal(t, 302, code)
966 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
968 t.Fatal("Did not find pattern in body: " +
969 signInSkipProvider + "\nBody:\n" + body)
973type ProcessCookieTest struct {
974 opts *options.Options
976 rw *httptest.ResponseRecorder
981type ProcessCookieTestOpts struct {
982 providerValidateCookieResponse bool
985type OptionsModifier func(*options.Options)
987func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) *ProcessCookieTest {
988 var pcTest ProcessCookieTest
990 pcTest.opts = options.NewOptions()
991 for _, modifier := range modifiers {
992 modifier(pcTest.opts)
994 pcTest.opts.ClientID = "asdfljk"
995 pcTest.opts.ClientSecret = "lkjfdsig"
996 pcTest.opts.Cookie.Secret = "0123456789abcdef0123456789abcdef"
997 // First, set the CookieRefresh option so proxy.AesCipher is created,
998 // needed to encrypt the access_token.
999 pcTest.opts.Cookie.Refresh = time.Hour
1000 validation.Validate(pcTest.opts)
1002 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1003 return pcTest.validateUser
1005 pcTest.proxy.provider = &TestProvider{
1006 ValidToken: opts.providerValidateCookieResponse,
1009 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
1010 // access_token validation.
1011 pcTest.proxy.CookieRefresh = time.Duration(0)
1012 pcTest.rw = httptest.NewRecorder()
1013 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
1014 pcTest.validateUser = true
1018func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
1019 return NewProcessCookieTest(ProcessCookieTestOpts{
1020 providerValidateCookieResponse: true,
1024func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) *ProcessCookieTest {
1025 return NewProcessCookieTest(ProcessCookieTestOpts{
1026 providerValidateCookieResponse: true,
1030func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
1031 err := p.proxy.SaveSession(p.rw, p.req, s)
1035 for _, cookie := range p.rw.Result().Cookies() {
1036 p.req.AddCookie(cookie)
1041func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
1042 return p.proxy.LoadCookiedSession(p.req)
1045func TestLoadCookiedSession(t *testing.T) {
1046 pcTest := NewProcessCookieTestWithDefaults()
1048 created := time.Now()
1049 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
1050 pcTest.SaveSession(startSession)
1052 session, err := pcTest.LoadCookiedSession()
1053 assert.Equal(t, nil, err)
1054 assert.Equal(t, startSession.Email, session.Email)
1055 assert.Equal(t, "", session.User)
1056 assert.Equal(t, startSession.AccessToken, session.AccessToken)
1059func TestProcessCookieNoCookieError(t *testing.T) {
1060 pcTest := NewProcessCookieTestWithDefaults()
1062 session, err := pcTest.LoadCookiedSession()
1063 assert.Equal(t, "cookie \"_oauth2_proxy\" not present", err.Error())
1065 t.Errorf("expected nil session. got %#v", session)
1069func TestProcessCookieRefreshNotSet(t *testing.T) {
1070 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1071 opts.Cookie.Expire = time.Duration(23) * time.Hour
1073 reference := time.Now().Add(time.Duration(-2) * time.Hour)
1075 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1076 pcTest.SaveSession(startSession)
1078 session, err := pcTest.LoadCookiedSession()
1079 assert.Equal(t, nil, err)
1080 if session.Age() < time.Duration(-2)*time.Hour {
1081 t.Errorf("cookie too young %v", session.Age())
1083 assert.Equal(t, startSession.Email, session.Email)
1086func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1087 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1088 opts.Cookie.Expire = time.Duration(24) * time.Hour
1090 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1091 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1092 pcTest.SaveSession(startSession)
1094 session, err := pcTest.LoadCookiedSession()
1095 assert.NotEqual(t, nil, err)
1097 t.Errorf("expected nil session %#v", session)
1101func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1102 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1103 opts.Cookie.Expire = time.Duration(24) * time.Hour
1105 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1106 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1107 pcTest.SaveSession(startSession)
1109 pcTest.proxy.CookieRefresh = time.Hour
1110 session, err := pcTest.LoadCookiedSession()
1111 assert.NotEqual(t, nil, err)
1113 t.Errorf("expected nil session %#v", session)
1117func NewUserInfoEndpointTest() *ProcessCookieTest {
1118 pcTest := NewProcessCookieTestWithDefaults()
1119 pcTest.req, _ = http.NewRequest("GET",
1120 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1124func TestUserInfoEndpointAccepted(t *testing.T) {
1125 test := NewUserInfoEndpointTest()
1126 startSession := &sessions.SessionState{
1127 Email: "john.doe@example.com", AccessToken: "my_access_token"}
1128 test.SaveSession(startSession)
1130 test.proxy.ServeHTTP(test.rw, test.req)
1131 assert.Equal(t, http.StatusOK, test.rw.Code)
1132 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1133 assert.Equal(t, "{\"email\":\"john.doe@example.com\"}\n", string(bodyBytes))
1136func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1137 test := NewUserInfoEndpointTest()
1139 test.proxy.ServeHTTP(test.rw, test.req)
1140 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1143func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) *ProcessCookieTest {
1144 pcTest := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1145 pcTest.req, _ = http.NewRequest("GET",
1146 pcTest.opts.ProxyPrefix+"/auth", nil)
1150func TestAuthOnlyEndpointAccepted(t *testing.T) {
1151 test := NewAuthOnlyEndpointTest()
1152 created := time.Now()
1153 startSession := &sessions.SessionState{
1154 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1155 test.SaveSession(startSession)
1157 test.proxy.ServeHTTP(test.rw, test.req)
1158 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1159 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1160 assert.Equal(t, "", string(bodyBytes))
1163func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1164 test := NewAuthOnlyEndpointTest()
1166 test.proxy.ServeHTTP(test.rw, test.req)
1167 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1168 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1169 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1172func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1173 test := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1174 opts.Cookie.Expire = time.Duration(24) * time.Hour
1176 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1177 startSession := &sessions.SessionState{
1178 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1179 test.SaveSession(startSession)
1181 test.proxy.ServeHTTP(test.rw, test.req)
1182 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1183 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1184 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1187func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1188 test := NewAuthOnlyEndpointTest()
1189 created := time.Now()
1190 startSession := &sessions.SessionState{
1191 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1192 test.SaveSession(startSession)
1193 test.validateUser = false
1195 test.proxy.ServeHTTP(test.rw, test.req)
1196 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1197 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1198 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1201func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1202 var pcTest ProcessCookieTest
1204 pcTest.opts = options.NewOptions()
1205 pcTest.opts.SetXAuthRequest = true
1206 validation.Validate(pcTest.opts)
1208 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1209 return pcTest.validateUser
1211 pcTest.proxy.provider = &TestProvider{
1215 pcTest.validateUser = true
1217 pcTest.rw = httptest.NewRecorder()
1218 pcTest.req, _ = http.NewRequest("GET",
1219 pcTest.opts.ProxyPrefix+"/auth", nil)
1221 created := time.Now()
1222 startSession := &sessions.SessionState{
1223 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1224 pcTest.SaveSession(startSession)
1226 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1227 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1228 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1229 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1232func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1233 var pcTest ProcessCookieTest
1235 pcTest.opts = options.NewOptions()
1236 pcTest.opts.SetXAuthRequest = true
1237 pcTest.opts.SetBasicAuth = true
1238 validation.Validate(pcTest.opts)
1240 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1241 return pcTest.validateUser
1243 pcTest.proxy.provider = &TestProvider{
1247 pcTest.validateUser = true
1249 pcTest.rw = httptest.NewRecorder()
1250 pcTest.req, _ = http.NewRequest("GET",
1251 pcTest.opts.ProxyPrefix+"/auth", nil)
1253 created := time.Now()
1254 startSession := &sessions.SessionState{
1255 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1256 pcTest.SaveSession(startSession)
1258 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1259 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1260 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1261 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1262 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:"+pcTest.opts.BasicAuthPassword))
1263 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1266func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1267 var pcTest ProcessCookieTest
1269 pcTest.opts = options.NewOptions()
1270 pcTest.opts.SetXAuthRequest = true
1271 pcTest.opts.SetBasicAuth = false
1272 validation.Validate(pcTest.opts)
1274 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1275 return pcTest.validateUser
1277 pcTest.proxy.provider = &TestProvider{
1281 pcTest.validateUser = true
1283 pcTest.rw = httptest.NewRecorder()
1284 pcTest.req, _ = http.NewRequest("GET",
1285 pcTest.opts.ProxyPrefix+"/auth", nil)
1287 created := time.Now()
1288 startSession := &sessions.SessionState{
1289 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1290 pcTest.SaveSession(startSession)
1292 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1293 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1294 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1295 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1296 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1299func TestAuthSkippedForPreflightRequests(t *testing.T) {
1300 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1302 w.Write([]byte("response"))
1304 defer upstream.Close()
1306 opts := options.NewOptions()
1307 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1308 opts.ClientID = "aljsal"
1309 opts.ClientSecret = "jglkfsdgj"
1310 opts.Cookie.Secret = base64CookieSecret
1311 opts.SkipAuthPreflight = true
1312 validation.Validate(opts)
1314 upstreamURL, _ := url.Parse(upstream.URL)
1315 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1317 proxy := NewOAuthProxy(opts, func(string) bool { return false })
1318 rw := httptest.NewRecorder()
1319 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1320 proxy.ServeHTTP(rw, req)
1322 assert.Equal(t, 200, rw.Code)
1323 assert.Equal(t, "response", rw.Body.String())
1326type SignatureAuthenticator struct {
1327 auth hmacauth.HmacAuth
1330func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1331 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1332 if result == hmacauth.ResultNoSignature {
1333 w.Write([]byte("no signature received"))
1334 } else if result == hmacauth.ResultMatch {
1335 w.Write([]byte("signatures match"))
1336 } else if result == hmacauth.ResultMismatch {
1337 w.Write([]byte("signatures do not match:" +
1338 "\n received: " + headerSig +
1339 "\n computed: " + computedSig))
1341 panic("Unknown result value: " + result.String())
1345type SignatureTest struct {
1346 opts *options.Options
1347 upstream *httptest.Server
1349 provider *httptest.Server
1351 rw *httptest.ResponseRecorder
1352 authenticator *SignatureAuthenticator
1355func NewSignatureTest() *SignatureTest {
1356 opts := options.NewOptions()
1357 opts.Cookie.Secret = rawCookieSecret
1358 opts.ClientID = "client ID"
1359 opts.ClientSecret = "client secret"
1360 opts.EmailDomains = []string{"acm.org"}
1362 authenticator := &SignatureAuthenticator{}
1363 upstream := httptest.NewServer(
1364 http.HandlerFunc(authenticator.Authenticate))
1365 upstreamURL, _ := url.Parse(upstream.URL)
1366 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1368 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1369 w.Write([]byte(`{"access_token": "my_auth_token"}`))
1371 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1372 providerURL, _ := url.Parse(provider.URL)
1373 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1375 return &SignatureTest{
1381 httptest.NewRecorder(),
1386func (st *SignatureTest) Close() {
1391// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1392// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1393// https://github.com/18F/hmacauth/pull/4
1394type fakeNetConn struct {
1398func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1399 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1400 copy(p, fnc.reqBody)
1402 return bodyLen, io.EOF
1407func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
1408 err := validation.Validate(st.opts)
1412 proxy := NewOAuthProxy(st.opts, func(email string) bool { return true })
1414 var bodyBuf io.ReadCloser
1416 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1418 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1419 req.Header = st.header
1421 state := &sessions.SessionState{
1422 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1423 err = proxy.SaveSession(st.rw, req, state)
1427 for _, c := range st.rw.Result().Cookies() {
1430 // This is used by the upstream to validate the signature.
1431 st.authenticator.auth = hmacauth.NewHmacAuth(
1432 crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
1433 proxy.ServeHTTP(st.rw, req)
1436func TestNoRequestSignature(t *testing.T) {
1437 st := NewSignatureTest()
1439 st.MakeRequestWithExpectedKey("GET", "", "")
1440 assert.Equal(t, 200, st.rw.Code)
1441 assert.Equal(t, st.rw.Body.String(), "no signature received")
1444func TestRequestSignatureGetRequest(t *testing.T) {
1445 st := NewSignatureTest()
1447 st.opts.SignatureKey = "sha1:7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d"
1448 st.MakeRequestWithExpectedKey("GET", "", "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d")
1449 assert.Equal(t, 200, st.rw.Code)
1450 assert.Equal(t, st.rw.Body.String(), "signatures match")
1453func TestRequestSignaturePostRequest(t *testing.T) {
1454 st := NewSignatureTest()
1456 st.opts.SignatureKey = "sha1:d90df39e2d19282840252612dd7c81421a372f61"
1457 payload := `{ "hello": "world!" }`
1458 st.MakeRequestWithExpectedKey("POST", payload, "d90df39e2d19282840252612dd7c81421a372f61")
1459 assert.Equal(t, 200, st.rw.Code)
1460 assert.Equal(t, st.rw.Body.String(), "signatures match")
1463func TestGetRedirect(t *testing.T) {
1464 options := options.NewOptions()
1465 _ = validation.Validate(options)
1466 require.NotEmpty(t, options.ProxyPrefix)
1467 proxy := NewOAuthProxy(options, func(s string) bool { return false })
1472 expectedRedirect string
1475 name: "request outside of ProxyPrefix redirects to original URL",
1477 expectedRedirect: "/foo/bar",
1480 name: "request under ProxyPrefix redirects to root",
1481 url: proxy.ProxyPrefix + "/foo/bar",
1482 expectedRedirect: "/",
1485 for _, tt := range tests {
1486 t.Run(tt.name, func(t *testing.T) {
1487 req, _ := http.NewRequest("GET", tt.url, nil)
1488 redirect, err := proxy.GetRedirect(req)
1490 assert.NoError(t, err)
1491 assert.Equal(t, tt.expectedRedirect, redirect)
1496type ajaxRequestTest struct {
1497 opts *options.Options
1501func newAjaxRequestTest() *ajaxRequestTest {
1502 test := &ajaxRequestTest{}
1503 test.opts = options.NewOptions()
1504 test.opts.Cookie.Secret = base64CookieSecret
1505 test.opts.ClientID = "gkljfdl"
1506 test.opts.ClientSecret = "sdflkjs"
1507 validation.Validate(test.opts)
1508 test.proxy = NewOAuthProxy(test.opts, func(email string) bool {
1514func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1515 rw := httptest.NewRecorder()
1516 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1521 test.proxy.ServeHTTP(rw, req)
1522 return rw.Code, rw.Header(), nil
1525func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1526 test := newAjaxRequestTest()
1529 code, rh, err := test.getEndpoint(endpoint, header)
1530 assert.NoError(t, err)
1531 assert.Equal(t, http.StatusUnauthorized, code)
1532 mime := rh.Get("Content-Type")
1533 assert.Equal(t, applicationJSON, mime)
1535func TestAjaxUnauthorizedRequest1(t *testing.T) {
1536 header := make(http.Header)
1537 header.Add("accept", applicationJSON)
1539 testAjaxUnauthorizedRequest(t, header)
1542func TestAjaxUnauthorizedRequest2(t *testing.T) {
1543 header := make(http.Header)
1544 header.Add("Accept", applicationJSON)
1546 testAjaxUnauthorizedRequest(t, header)
1549func TestAjaxForbiddendRequest(t *testing.T) {
1550 test := newAjaxRequestTest()
1552 header := make(http.Header)
1553 code, rh, err := test.getEndpoint(endpoint, header)
1554 assert.NoError(t, err)
1555 assert.Equal(t, http.StatusForbidden, code)
1556 mime := rh.Get("Content-Type")
1557 assert.NotEqual(t, applicationJSON, mime)
1560func TestClearSplitCookie(t *testing.T) {
1561 opts := options.NewOptions()
1562 opts.Cookie.Name = "oauth2"
1563 opts.Cookie.Domains = []string{"abc"}
1564 store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1565 assert.Equal(t, err, nil)
1566 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1567 var rw = httptest.NewRecorder()
1568 req := httptest.NewRequest("get", "/", nil)
1570 req.AddCookie(&http.Cookie{
1574 req.AddCookie(&http.Cookie{
1578 req.AddCookie(&http.Cookie{
1583 p.ClearSessionCookie(rw, req)
1584 header := rw.Header()
1586 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1589func TestClearSingleCookie(t *testing.T) {
1590 opts := options.NewOptions()
1591 opts.Cookie.Name = "oauth2"
1592 opts.Cookie.Domains = []string{"abc"}
1593 store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1594 assert.Equal(t, err, nil)
1595 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1596 var rw = httptest.NewRecorder()
1597 req := httptest.NewRequest("get", "/", nil)
1599 req.AddCookie(&http.Cookie{
1603 req.AddCookie(&http.Cookie{
1608 p.ClearSessionCookie(rw, req)
1609 header := rw.Header()
1611 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1614type NoOpKeySet struct {
1617func (NoOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
1618 splitStrings := strings.Split(jwt, ".")
1619 payloadString := splitStrings[1]
1620 return base64.RawURLEncoding.DecodeString(payloadString)
1623func TestGetJwtSession(t *testing.T) {
1626 "sub": "1234567890",
1627 "aud": "https://test.myapp.com",
1629 "email": "john@example.com",
1630 "iss": "https://issuer.example.com",
1635 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1636 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1637 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1638 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1639 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1640 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1642 keyset := NoOpKeySet{}
1643 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1644 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1646 test := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1647 opts.PassAuthorization = true
1648 opts.SetAuthorization = true
1649 opts.SetXAuthRequest = true
1650 opts.SkipJwtBearerTokens = true
1651 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
1653 tp, _ := test.proxy.provider.(*TestProvider)
1654 tp.GroupValidator = func(s string) bool {
1658 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1659 test.req.Header = map[string][]string{
1660 "Authorization": {authHeader},
1664 expires := time.Unix(1912151821, 0)
1665 session, _ := test.proxy.GetJwtSession(test.req)
1666 assert.Equal(t, session.User, "1234567890")
1667 assert.Equal(t, session.Email, "john@example.com")
1668 assert.Equal(t, session.ExpiresOn, &expires)
1669 assert.Equal(t, session.IDToken, goodJwt)
1671 test.proxy.ServeHTTP(test.rw, test.req)
1672 if test.rw.Code >= 400 {
1673 t.Fatalf("expected 3xx got %d", test.rw.Code)
1676 // Check PassAuthorization, should overwrite Basic header
1677 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
1678 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "1234567890")
1679 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
1681 // SetAuthorization and SetXAuthRequest
1682 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
1683 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "1234567890")
1684 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
1687func TestFindJwtBearerToken(t *testing.T) {
1688 p := OAuthProxy{CookieName: "oauth2", CookieDomains: []string{"abc"}}
1689 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}}
1691 validToken := "eyJfoobar.eyJfoobar.12345asdf"
1695 getReq.Header = map[string][]string{
1696 "Authorization": {fmt.Sprintf("Bearer %s", validToken)},
1699 token, _ = p.findBearerToken(getReq)
1700 assert.Equal(t, validToken, token)
1702 // Basic - no password
1703 getReq.SetBasicAuth(token, "")
1704 token, _ = p.findBearerToken(getReq)
1705 assert.Equal(t, validToken, token)
1707 // Basic - sentinel password
1708 getReq.SetBasicAuth(token, "x-oauth-basic")
1709 token, _ = p.findBearerToken(getReq)
1710 assert.Equal(t, validToken, token)
1712 // Basic - any username, password matching jwt pattern
1713 getReq.SetBasicAuth("any-username-you-could-wish-for", token)
1714 token, _ = p.findBearerToken(getReq)
1715 assert.Equal(t, validToken, token)
1717 failures := []string{
1719 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1721 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA",
1722 // Invalid encrypted key
1723 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.//////.dGVzdA.dGVzdA.dGVzdA",
1725 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.//////.dGVzdA.dGVzdA",
1726 // Invalid ciphertext
1727 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.//////.dGVzdA",
1729 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.//////",
1731 "W10.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1733 "######.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1734 // Missing alc/enc params
1735 "e30.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1738 for _, failure := range failures {
1739 getReq.Header = map[string][]string{
1740 "Authorization": {fmt.Sprintf("Bearer %s", failure)},
1742 _, err := p.findBearerToken(getReq)
1743 assert.Error(t, err)
1746 fmt.Printf("%s", token)
1749func Test_prepareNoCache(t *testing.T) {
1750 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1753 mux := http.NewServeMux()
1754 mux.Handle("/", handler)
1756 rec := httptest.NewRecorder()
1757 req := httptest.NewRequest(http.MethodGet, "/", nil)
1758 mux.ServeHTTP(rec, req)
1760 for k, v := range noCacheHeaders {
1761 assert.Equal(t, rec.Header().Get(k), v)
1765func Test_noCacheHeadersDoesNotExistsInResponseHeadersFromUpstream(t *testing.T) {
1766 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1767 w.Write([]byte("upstream"))
1769 t.Cleanup(upstream.Close)
1771 opts := options.NewOptions()
1772 opts.Upstreams = []string{upstream.URL}
1773 opts.SkipAuthRegex = []string{".*"}
1774 _ = validation.Validate(opts)
1775 proxy := NewOAuthProxy(opts, func(email string) bool {
1779 rec := httptest.NewRecorder()
1780 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
1781 proxy.ServeHTTP(rec, req)
1783 assert.Equal(t, http.StatusOK, rec.Code)
1784 assert.Equal(t, "upstream", rec.Body.String())
1786 // checking noCacheHeaders does not exists in response headers from upstream
1787 for k := range noCacheHeaders {
1788 assert.Equal(t, "", rec.Header().Get(k))
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