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 sessionscookie "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 = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpams"
39 clientID = "3984n253984d7348dm8234yf982t"
40 clientSecret = "gv3498mfc9t23y23974dm2394dm9"
44 logger.SetFlags(logger.Lshortfile)
47type WebSocketOrRestHandler struct {
48 restHandler http.Handler
49 wsHandler http.Handler
52func (h *WebSocketOrRestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
53 if r.Header.Get("Upgrade") == "websocket" {
54 h.wsHandler.ServeHTTP(w, r)
56 h.restHandler.ServeHTTP(w, r)
60func TestWebSocketProxy(t *testing.T) {
61 handler := WebSocketOrRestHandler{
62 restHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
64 hostname, _, _ := net.SplitHostPort(r.Host)
65 _, err := w.Write([]byte(hostname))
70 wsHandler: websocket.Handler(func(ws *websocket.Conn) {
71 defer func(t *testing.T) {
72 if err := ws.Close(); err != nil {
77 err := websocket.Message.Receive(ws, &data)
81 err = websocket.Message.Send(ws, data)
87 backend := httptest.NewServer(&handler)
88 t.Cleanup(backend.Close)
90 backendURL, _ := url.Parse(backend.URL)
92 opts := baseTestOptions()
93 var auth hmacauth.HmacAuth
94 opts.PassHostHeader = true
95 proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, opts, auth)
96 frontend := httptest.NewServer(proxyHandler)
97 t.Cleanup(frontend.Close)
99 frontendURL, _ := url.Parse(frontend.URL)
100 frontendWSURL := "ws://" + frontendURL.Host + "/"
102 ws, err := websocket.Dial(frontendWSURL, "", "http://localhost/")
106 request := []byte("hello, world!")
107 err = websocket.Message.Send(ws, request)
111 var response = make([]byte, 1024)
112 err = websocket.Message.Receive(ws, &response)
116 if g, e := string(request), string(response); g != e {
117 t.Errorf("got body %q; expected %q", g, e)
120 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
121 res, _ := http.DefaultClient.Do(getReq)
122 bodyBytes, _ := ioutil.ReadAll(res.Body)
123 backendHostname, _, _ := net.SplitHostPort(backendURL.Host)
124 if g, e := string(bodyBytes), backendHostname; g != e {
125 t.Errorf("got body %q; expected %q", g, e)
129func TestNewReverseProxy(t *testing.T) {
130 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
132 hostname, _, _ := net.SplitHostPort(r.Host)
133 _, err := w.Write([]byte(hostname))
138 t.Cleanup(backend.Close)
140 backendURL, _ := url.Parse(backend.URL)
141 backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host)
142 backendHost := net.JoinHostPort(backendHostname, backendPort)
143 proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
145 proxyHandler := NewReverseProxy(proxyURL, &options.Options{FlushInterval: time.Second})
146 setProxyUpstreamHostHeader(proxyHandler, proxyURL)
147 frontend := httptest.NewServer(proxyHandler)
148 t.Cleanup(frontend.Close)
150 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
151 res, _ := http.DefaultClient.Do(getReq)
152 bodyBytes, _ := ioutil.ReadAll(res.Body)
153 if g, e := string(bodyBytes), backendHostname; g != e {
154 t.Errorf("got body %q; expected %q", g, e)
158func TestEncodedSlashes(t *testing.T) {
160 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
164 t.Cleanup(backend.Close)
166 b, _ := url.Parse(backend.URL)
167 proxyHandler := NewReverseProxy(b, &options.Options{FlushInterval: time.Second})
168 setProxyDirector(proxyHandler)
169 frontend := httptest.NewServer(proxyHandler)
170 t.Cleanup(frontend.Close)
172 f, _ := url.Parse(frontend.URL)
173 encodedPath := "/a%2Fb/?c=1"
174 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: f.Host, Opaque: encodedPath}}
175 _, err := http.DefaultClient.Do(getReq)
179 if seen != encodedPath {
180 t.Errorf("got bad request %q expected %q", seen, encodedPath)
184func TestRobotsTxt(t *testing.T) {
185 opts := baseTestOptions()
186 err := validation.Validate(opts)
187 assert.NoError(t, err)
189 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
193 rw := httptest.NewRecorder()
194 req, _ := http.NewRequest("GET", "/robots.txt", nil)
195 proxy.ServeHTTP(rw, req)
196 assert.Equal(t, 200, rw.Code)
197 assert.Equal(t, "User-agent: *\nDisallow: /", rw.Body.String())
200func TestIsValidRedirect(t *testing.T) {
201 opts := baseTestOptions()
202 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
203 opts.WhitelistDomains = []string{
207 ".sub.port.bar:8080",
209 ".sub.anyport.bar:*",
211 err := validation.Validate(opts)
212 assert.NoError(t, err)
214 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
219 testCases := []struct {
220 Desc, Redirect string
226 ExpectedResult: false,
230 Redirect: "/redirect",
231 ExpectedResult: true,
235 Redirect: "//redirect",
236 ExpectedResult: false,
240 Redirect: "http://foo.bar/redirect",
241 ExpectedResult: true,
245 Redirect: "https://foo.bar/redirect",
246 ExpectedResult: true,
249 Desc: "invalidHTTPSubdomain",
250 Redirect: "http://baz.foo.bar/redirect",
251 ExpectedResult: false,
254 Desc: "invalidHTTPSSubdomain",
255 Redirect: "https://baz.foo.bar/redirect",
256 ExpectedResult: false,
259 Desc: "validHTTPSubdomain",
260 Redirect: "http://baz.bar.foo/redirect",
261 ExpectedResult: true,
264 Desc: "validHTTPSSubdomain",
265 Redirect: "https://baz.bar.foo/redirect",
266 ExpectedResult: true,
269 Desc: "validHTTPDomain",
270 Redirect: "http://bar.foo/redirect",
271 ExpectedResult: true,
274 Desc: "invalidHTTP1",
275 Redirect: "http://foo.bar.evil.corp/redirect",
276 ExpectedResult: false,
279 Desc: "invalidHTTPS1",
280 Redirect: "https://foo.bar.evil.corp/redirect",
281 ExpectedResult: false,
284 Desc: "invalidHTTP2",
285 Redirect: "http://evil.corp/redirect?rd=foo.bar",
286 ExpectedResult: false,
289 Desc: "invalidHTTPS2",
290 Redirect: "https://evil.corp/redirect?rd=foo.bar",
291 ExpectedResult: false,
295 Redirect: "https://evil.corp:3838/redirect",
296 ExpectedResult: false,
299 Desc: "invalidEmptyPort",
300 Redirect: "http://foo.bar:3838/redirect",
301 ExpectedResult: false,
304 Desc: "invalidEmptyPortSubdomain",
305 Redirect: "http://baz.bar.foo:3838/redirect",
306 ExpectedResult: false,
309 Desc: "validSpecificPort",
310 Redirect: "http://port.bar:8080/redirect",
311 ExpectedResult: true,
314 Desc: "invalidSpecificPort",
315 Redirect: "http://port.bar:3838/redirect",
316 ExpectedResult: false,
319 Desc: "validSpecificPortSubdomain",
320 Redirect: "http://foo.sub.port.bar:8080/redirect",
321 ExpectedResult: true,
324 Desc: "invalidSpecificPortSubdomain",
325 Redirect: "http://foo.sub.port.bar:3838/redirect",
326 ExpectedResult: false,
329 Desc: "validAnyPort1",
330 Redirect: "http://anyport.bar:8080/redirect",
331 ExpectedResult: true,
334 Desc: "validAnyPort2",
335 Redirect: "http://anyport.bar:8081/redirect",
336 ExpectedResult: true,
339 Desc: "validAnyPortSubdomain1",
340 Redirect: "http://a.sub.anyport.bar:8080/redirect",
341 ExpectedResult: true,
344 Desc: "validAnyPortSubdomain2",
345 Redirect: "http://a.sub.anyport.bar:8081/redirect",
346 ExpectedResult: true,
349 Desc: "openRedirect1",
350 Redirect: "/\\evil.com",
351 ExpectedResult: false,
354 Desc: "openRedirectSpace1",
355 Redirect: "/ /evil.com",
356 ExpectedResult: false,
359 Desc: "openRedirectSpace2",
360 Redirect: "/ \\evil.com",
361 ExpectedResult: false,
364 Desc: "openRedirectTab1",
365 Redirect: "/\t/evil.com",
366 ExpectedResult: false,
369 Desc: "openRedirectTab2",
370 Redirect: "/\t\\evil.com",
371 ExpectedResult: false,
374 Desc: "openRedirectVerticalTab1",
375 Redirect: "/\v/evil.com",
376 ExpectedResult: false,
379 Desc: "openRedirectVerticalTab2",
380 Redirect: "/\v\\evil.com",
381 ExpectedResult: false,
384 Desc: "openRedirectNewLine1",
385 Redirect: "/\n/evil.com",
386 ExpectedResult: false,
389 Desc: "openRedirectNewLine2",
390 Redirect: "/\n\\evil.com",
391 ExpectedResult: false,
394 Desc: "openRedirectCarriageReturn1",
395 Redirect: "/\r/evil.com",
396 ExpectedResult: false,
399 Desc: "openRedirectCarriageReturn2",
400 Redirect: "/\r\\evil.com",
401 ExpectedResult: false,
404 Desc: "openRedirectTripleTab",
405 Redirect: "/\t\t/\t/evil.com",
406 ExpectedResult: false,
409 Desc: "openRedirectTripleTab2",
410 Redirect: "/\t\t\\\t/evil.com",
411 ExpectedResult: false,
414 Desc: "openRedirectQuadTab1",
415 Redirect: "/\t\t/\t\t\\evil.com",
416 ExpectedResult: false,
419 Desc: "openRedirectQuadTab2",
420 Redirect: "/\t\t\\\t\t/evil.com",
421 ExpectedResult: false,
424 Desc: "openRedirectPeriod1",
425 Redirect: "/./\\evil.com",
426 ExpectedResult: false,
429 Desc: "openRedirectPeriod2",
430 Redirect: "/./../../\\evil.com",
431 ExpectedResult: false,
434 Desc: "openRedirectDoubleTab",
435 Redirect: "/\t/\t\\evil.com",
436 ExpectedResult: false,
440 for _, tc := range testCases {
441 t.Run(tc.Desc, func(t *testing.T) {
442 result := proxy.IsValidRedirect(tc.Redirect)
444 if result != tc.ExpectedResult {
445 t.Errorf("expected %t got %t", tc.ExpectedResult, result)
451func TestOpenRedirects(t *testing.T) {
452 opts := baseTestOptions()
453 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
454 opts.WhitelistDomains = []string{
458 ".sub.port.bar:8080",
460 ".sub.anyport.bar:*",
461 "www.whitelisteddomain.tld",
463 err := validation.Validate(opts)
464 assert.NoError(t, err)
466 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
471 file, err := os.Open("./test/openredirects.txt")
475 defer func(t *testing.T) {
476 if err := file.Close(); err != nil {
481 scanner := bufio.NewScanner(file)
484 t.Run(rd, func(t *testing.T) {
485 rdUnescaped, err := url.QueryUnescape(rd)
489 if proxy.IsValidRedirect(rdUnescaped) {
490 t.Errorf("Expected %q to not be valid (unescaped: %q)", rd, rdUnescaped)
495 if err := scanner.Err(); err != nil {
500type TestProvider struct {
501 *providers.ProviderData
504 GroupValidator func(string) bool
507var _ providers.Provider = (*TestProvider)(nil)
509func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
510 return &TestProvider{
511 ProviderData: &providers.ProviderData{
512 ProviderName: "Test Provider",
515 Host: providerURL.Host,
516 Path: "/oauth/authorize",
520 Host: providerURL.Host,
521 Path: "/oauth/token",
523 ProfileURL: &url.URL{
525 Host: providerURL.Host,
526 Path: "/api/v1/profile",
528 Scope: "profile.email",
530 EmailAddress: emailAddress,
531 GroupValidator: func(s string) bool {
537func (tp *TestProvider) GetEmailAddress(ctx context.Context, session *sessions.SessionState) (string, error) {
538 return tp.EmailAddress, nil
541func (tp *TestProvider) ValidateSessionState(ctx context.Context, session *sessions.SessionState) bool {
545func TestBasicAuthPassword(t *testing.T) {
546 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
547 logger.Printf("%#v", r)
551 payload = `{"access_token": "my_auth_token"}`
553 payload = r.Header.Get("Authorization")
555 payload = "No Authorization header found."
559 _, err := w.Write([]byte(payload))
564 opts := baseTestOptions()
565 opts.Upstreams = append(opts.Upstreams, providerServer.URL)
566 opts.Cookie.Secure = false
567 opts.PassBasicAuth = true
568 opts.SetBasicAuth = true
569 opts.PassUserHeaders = true
570 opts.PreferEmailToUser = true
571 opts.BasicAuthPassword = "This is a secure password"
572 err := validation.Validate(opts)
573 assert.NoError(t, err)
575 providerURL, _ := url.Parse(providerServer.URL)
576 const emailAddress = "john.doe@example.com"
578 opts.SetProvider(NewTestProvider(providerURL, emailAddress))
579 proxy, err := NewOAuthProxy(opts, func(email string) bool {
580 return email == emailAddress
586 rw := httptest.NewRecorder()
587 req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:", strings.NewReader(""))
588 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
589 proxy.ServeHTTP(rw, req)
591 t.Fatalf("expected 3xx got %d", rw.Code)
593 cookie := rw.Header().Values("Set-Cookie")[1]
595 cookieName := proxy.CookieName
597 keyPrefix := cookieName + "="
599 for _, field := range strings.Split(cookie, "; ") {
600 value = strings.TrimPrefix(field, keyPrefix)
608 req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
609 req.AddCookie(&http.Cookie{
613 Expires: time.Now().Add(time.Duration(24)),
616 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
618 rw = httptest.NewRecorder()
619 proxy.ServeHTTP(rw, req)
621 // The username in the basic auth credentials is expected to be equal to the email address from the
622 // auth response, so we use the same variable here.
623 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
624 assert.Equal(t, expectedHeader, rw.Body.String())
625 providerServer.Close()
628func TestBasicAuthWithEmail(t *testing.T) {
629 opts := baseTestOptions()
630 opts.PassBasicAuth = true
631 opts.PassUserHeaders = false
632 opts.PreferEmailToUser = false
633 opts.BasicAuthPassword = "This is a secure password"
634 err := validation.Validate(opts)
635 assert.NoError(t, err)
637 const emailAddress = "john.doe@example.com"
638 const userName = "9fcab5c9b889a557"
640 // The username in the basic auth credentials is expected to be equal to the email address from the
641 expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
642 expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
644 created := time.Now()
645 session := &sessions.SessionState{
648 AccessToken: "oauth_token",
652 rw := httptest.NewRecorder()
653 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
654 proxy, err := NewOAuthProxy(opts, func(email string) bool {
655 return email == emailAddress
660 proxy.addHeadersForProxying(rw, req, session)
661 assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
662 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
665 opts.PreferEmailToUser = true
667 rw := httptest.NewRecorder()
668 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
670 proxy, err := NewOAuthProxy(opts, func(email string) bool {
671 return email == emailAddress
676 proxy.addHeadersForProxying(rw, req, session)
677 assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
678 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
682func TestPassUserHeadersWithEmail(t *testing.T) {
683 opts := baseTestOptions()
684 err := validation.Validate(opts)
685 assert.NoError(t, err)
687 const emailAddress = "john.doe@example.com"
688 const userName = "9fcab5c9b889a557"
690 created := time.Now()
691 session := &sessions.SessionState{
694 AccessToken: "oauth_token",
698 rw := httptest.NewRecorder()
699 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
700 proxy, err := NewOAuthProxy(opts, func(email string) bool {
701 return email == emailAddress
706 proxy.addHeadersForProxying(rw, req, session)
707 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
710 opts.PreferEmailToUser = true
712 rw := httptest.NewRecorder()
713 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
715 proxy, err := NewOAuthProxy(opts, func(email string) bool {
716 return email == emailAddress
721 proxy.addHeadersForProxying(rw, req, session)
722 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
726func TestStripAuthHeaders(t *testing.T) {
727 testCases := map[string]struct {
728 SkipAuthStripHeaders bool
732 PassAuthorization bool
733 StrippedHeaders map[string]bool
736 SkipAuthStripHeaders: true,
738 PassUserHeaders: true,
739 PassAccessToken: false,
740 PassAuthorization: false,
741 StrippedHeaders: map[string]bool{
742 "X-Forwarded-User": true,
743 "X-Forwarded-Email": true,
744 "X-Forwarded-Preferred-Username": true,
745 "X-Forwarded-Access-Token": false,
746 "Authorization": true,
749 "Pass access token": {
750 SkipAuthStripHeaders: true,
752 PassUserHeaders: true,
753 PassAccessToken: true,
754 PassAuthorization: false,
755 StrippedHeaders: map[string]bool{
756 "X-Forwarded-User": true,
757 "X-Forwarded-Email": true,
758 "X-Forwarded-Preferred-Username": true,
759 "X-Forwarded-Access-Token": true,
760 "Authorization": true,
763 "Nothing setting Authorization": {
764 SkipAuthStripHeaders: true,
765 PassBasicAuth: false,
766 PassUserHeaders: true,
767 PassAccessToken: true,
768 PassAuthorization: false,
769 StrippedHeaders: map[string]bool{
770 "X-Forwarded-User": true,
771 "X-Forwarded-Email": true,
772 "X-Forwarded-Preferred-Username": true,
773 "X-Forwarded-Access-Token": true,
774 "Authorization": false,
777 "Only Authorization header modified": {
778 SkipAuthStripHeaders: true,
779 PassBasicAuth: false,
780 PassUserHeaders: false,
781 PassAccessToken: false,
782 PassAuthorization: true,
783 StrippedHeaders: map[string]bool{
784 "X-Forwarded-User": false,
785 "X-Forwarded-Email": false,
786 "X-Forwarded-Preferred-Username": false,
787 "X-Forwarded-Access-Token": false,
788 "Authorization": true,
791 "Don't strip any headers (default options)": {
792 SkipAuthStripHeaders: false,
794 PassUserHeaders: true,
795 PassAccessToken: false,
796 PassAuthorization: false,
797 StrippedHeaders: map[string]bool{
798 "X-Forwarded-User": false,
799 "X-Forwarded-Email": false,
800 "X-Forwarded-Preferred-Username": false,
801 "X-Forwarded-Access-Token": false,
802 "Authorization": false,
805 "Don't strip any headers (custom options)": {
806 SkipAuthStripHeaders: false,
808 PassUserHeaders: true,
809 PassAccessToken: true,
810 PassAuthorization: false,
811 StrippedHeaders: map[string]bool{
812 "X-Forwarded-User": false,
813 "X-Forwarded-Email": false,
814 "X-Forwarded-Preferred-Username": false,
815 "X-Forwarded-Access-Token": false,
816 "Authorization": false,
821 initialHeaders := map[string]string{
822 "X-Forwarded-User": "9fcab5c9b889a557",
823 "X-Forwarded-Email": "john.doe@example.com",
824 "X-Forwarded-Preferred-Username": "john.doe",
825 "X-Forwarded-Access-Token": "AccessToken",
826 "Authorization": "bearer IDToken",
829 for name, tc := range testCases {
830 t.Run(name, func(t *testing.T) {
831 opts := baseTestOptions()
832 opts.SkipAuthStripHeaders = tc.SkipAuthStripHeaders
833 opts.PassBasicAuth = tc.PassBasicAuth
834 opts.PassUserHeaders = tc.PassUserHeaders
835 opts.PassAccessToken = tc.PassAccessToken
836 opts.PassAuthorization = tc.PassAuthorization
837 err := validation.Validate(opts)
838 assert.NoError(t, err)
840 req, _ := http.NewRequest("GET", fmt.Sprintf("%s/testCase", opts.ProxyPrefix), nil)
841 for header, val := range initialHeaders {
842 req.Header.Set(header, val)
845 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
846 assert.NoError(t, err)
847 if proxy.skipAuthStripHeaders {
848 proxy.stripAuthHeaders(req)
851 for header, stripped := range tc.StrippedHeaders {
853 assert.Equal(t, req.Header.Get(header), "")
855 assert.Equal(t, req.Header.Get(header), initialHeaders[header])
862type PassAccessTokenTest struct {
863 providerServer *httptest.Server
865 opts *options.Options
868type PassAccessTokenTestOptions struct {
873func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTest, error) {
874 patt := &PassAccessTokenTest{}
876 patt.providerServer = httptest.NewServer(
877 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
881 payload = `{"access_token": "my_auth_token"}`
883 payload = r.Header.Get("X-Forwarded-Access-Token")
885 payload = "No access token found."
889 _, err := w.Write([]byte(payload))
895 patt.opts = baseTestOptions()
896 patt.opts.Upstreams = append(patt.opts.Upstreams, patt.providerServer.URL)
897 if opts.ProxyUpstream != "" {
898 patt.opts.Upstreams = append(patt.opts.Upstreams, opts.ProxyUpstream)
900 patt.opts.Cookie.Secure = false
901 patt.opts.PassAccessToken = opts.PassAccessToken
902 err := validation.Validate(patt.opts)
907 providerURL, _ := url.Parse(patt.providerServer.URL)
908 const emailAddress = "michael.bland@gsa.gov"
910 patt.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
911 patt.proxy, err = NewOAuthProxy(patt.opts, func(email string) bool {
912 return email == emailAddress
920func (patTest *PassAccessTokenTest) Close() {
921 patTest.providerServer.Close()
924func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
926 rw := httptest.NewRecorder()
927 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
928 strings.NewReader(""))
932 req.AddCookie(patTest.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
933 patTest.proxy.ServeHTTP(rw, req)
934 return rw.Code, rw.Header().Values("Set-Cookie")[1]
937// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
938// and cookie and returns body and status code.
939func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
940 cookieName := patTest.proxy.CookieName
942 keyPrefix := cookieName + "="
944 for _, field := range strings.Split(cookie, "; ") {
945 value = strings.TrimPrefix(field, keyPrefix)
956 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
960 req.AddCookie(&http.Cookie{
964 Expires: time.Now().Add(time.Duration(24)),
968 rw := httptest.NewRecorder()
969 patTest.proxy.ServeHTTP(rw, req)
970 return rw.Code, rw.Body.String()
973func TestForwardAccessTokenUpstream(t *testing.T) {
974 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
975 PassAccessToken: true,
980 t.Cleanup(patTest.Close)
982 // A successful validation will redirect and set the auth cookie.
983 code, cookie := patTest.getCallbackEndpoint()
985 t.Fatalf("expected 302; got %d", code)
987 assert.NotNil(t, cookie)
989 // Now we make a regular request; the access_token from the cookie is
990 // forwarded as the "X-Forwarded-Access-Token" header. The token is
991 // read by the test provider server and written in the response body.
992 code, payload := patTest.getEndpointWithCookie(cookie, "/")
994 t.Fatalf("expected 200; got %d", code)
996 assert.Equal(t, "my_auth_token", payload)
999func TestStaticProxyUpstream(t *testing.T) {
1000 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
1001 PassAccessToken: true,
1002 ProxyUpstream: "static://200/static-proxy",
1007 t.Cleanup(patTest.Close)
1009 // A successful validation will redirect and set the auth cookie.
1010 code, cookie := patTest.getCallbackEndpoint()
1012 t.Fatalf("expected 302; got %d", code)
1014 assert.NotEqual(t, nil, cookie)
1016 // Now we make a regular request against the upstream proxy; And validate
1017 // the returned status code through the static proxy.
1018 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
1020 t.Fatalf("expected 200; got %d", code)
1022 assert.Equal(t, "Authenticated", payload)
1025func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
1026 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
1027 PassAccessToken: false,
1032 t.Cleanup(patTest.Close)
1034 // A successful validation will redirect and set the auth cookie.
1035 code, cookie := patTest.getCallbackEndpoint()
1037 t.Fatalf("expected 302; got %d", code)
1039 assert.NotEqual(t, nil, cookie)
1041 // Now we make a regular request, but the access token header should
1043 code, payload := patTest.getEndpointWithCookie(cookie, "/")
1045 t.Fatalf("expected 200; got %d", code)
1047 assert.Equal(t, "No access token found.", payload)
1050type SignInPageTest struct {
1051 opts *options.Options
1053 signInRegexp *regexp.Regexp
1054 signInProviderRegexp *regexp.Regexp
1057const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
1058const signInSkipProvider = `>Found<`
1060func NewSignInPageTest(skipProvider bool) (*SignInPageTest, error) {
1061 var sipTest SignInPageTest
1063 sipTest.opts = baseTestOptions()
1064 sipTest.opts.SkipProviderButton = skipProvider
1065 err := validation.Validate(sipTest.opts)
1070 sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
1076 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
1077 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
1079 return &sipTest, nil
1082func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
1083 rw := httptest.NewRecorder()
1084 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
1085 sipTest.proxy.ServeHTTP(rw, req)
1086 return rw.Code, rw.Body.String()
1089func TestSignInPageIncludesTargetRedirect(t *testing.T) {
1090 sipTest, err := NewSignInPageTest(false)
1094 const endpoint = "/some/random/endpoint"
1096 code, body := sipTest.GetEndpoint(endpoint)
1097 assert.Equal(t, 403, code)
1099 match := sipTest.signInRegexp.FindStringSubmatch(body)
1101 t.Fatal("Did not find pattern in body: " +
1102 signInRedirectPattern + "\nBody:\n" + body)
1104 if match[1] != endpoint {
1105 t.Fatal(`expected redirect to "` + endpoint +
1106 `", but was "` + match[1] + `"`)
1110func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
1111 sipTest, err := NewSignInPageTest(false)
1115 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
1116 assert.Equal(t, 200, code)
1118 match := sipTest.signInRegexp.FindStringSubmatch(body)
1120 t.Fatal("Did not find pattern in body: " +
1121 signInRedirectPattern + "\nBody:\n" + body)
1123 if match[1] != "/" {
1124 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
1128func TestSignInPageSkipProvider(t *testing.T) {
1129 sipTest, err := NewSignInPageTest(true)
1134 endpoint := "/some/random/endpoint"
1136 code, body := sipTest.GetEndpoint(endpoint)
1137 assert.Equal(t, 302, code)
1139 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
1141 t.Fatal("Did not find pattern in body: " +
1142 signInSkipProvider + "\nBody:\n" + body)
1146func TestSignInPageSkipProviderDirect(t *testing.T) {
1147 sipTest, err := NewSignInPageTest(true)
1152 endpoint := "/sign_in"
1154 code, body := sipTest.GetEndpoint(endpoint)
1155 assert.Equal(t, 302, code)
1157 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
1159 t.Fatal("Did not find pattern in body: " +
1160 signInSkipProvider + "\nBody:\n" + body)
1164type ProcessCookieTest struct {
1165 opts *options.Options
1167 rw *httptest.ResponseRecorder
1172type ProcessCookieTestOpts struct {
1173 providerValidateCookieResponse bool
1176type OptionsModifier func(*options.Options)
1178func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1179 var pcTest ProcessCookieTest
1181 pcTest.opts = baseTestOptions()
1182 for _, modifier := range modifiers {
1183 modifier(pcTest.opts)
1185 // First, set the CookieRefresh option so proxy.AesCipher is created,
1186 // needed to encrypt the access_token.
1187 pcTest.opts.Cookie.Refresh = time.Hour
1188 err := validation.Validate(pcTest.opts)
1193 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1194 return pcTest.validateUser
1199 pcTest.proxy.provider = &TestProvider{
1200 ValidToken: opts.providerValidateCookieResponse,
1203 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
1204 // access_token validation.
1205 pcTest.proxy.CookieRefresh = time.Duration(0)
1206 pcTest.rw = httptest.NewRecorder()
1207 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
1208 pcTest.validateUser = true
1212func NewProcessCookieTestWithDefaults() (*ProcessCookieTest, error) {
1213 return NewProcessCookieTest(ProcessCookieTestOpts{
1214 providerValidateCookieResponse: true,
1218func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1219 return NewProcessCookieTest(ProcessCookieTestOpts{
1220 providerValidateCookieResponse: true,
1224func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
1225 err := p.proxy.SaveSession(p.rw, p.req, s)
1229 for _, cookie := range p.rw.Result().Cookies() {
1230 p.req.AddCookie(cookie)
1235func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
1236 return p.proxy.LoadCookiedSession(p.req)
1239func TestLoadCookiedSession(t *testing.T) {
1240 pcTest, err := NewProcessCookieTestWithDefaults()
1245 created := time.Now()
1246 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
1247 err = pcTest.SaveSession(startSession)
1248 assert.NoError(t, err)
1250 session, err := pcTest.LoadCookiedSession()
1254 assert.Equal(t, startSession.Email, session.Email)
1255 assert.Equal(t, "", session.User)
1256 assert.Equal(t, startSession.AccessToken, session.AccessToken)
1259func TestProcessCookieNoCookieError(t *testing.T) {
1260 pcTest, err := NewProcessCookieTestWithDefaults()
1265 session, err := pcTest.LoadCookiedSession()
1266 assert.Error(t, err, "cookie \"_oauth2_proxy\" not present")
1268 t.Errorf("expected nil session. got %#v", session)
1272func TestProcessCookieRefreshNotSet(t *testing.T) {
1273 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1274 opts.Cookie.Expire = time.Duration(23) * time.Hour
1280 reference := time.Now().Add(time.Duration(-2) * time.Hour)
1282 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1283 err = pcTest.SaveSession(startSession)
1284 assert.NoError(t, err)
1286 session, err := pcTest.LoadCookiedSession()
1287 assert.Equal(t, nil, err)
1288 if session.Age() < time.Duration(-2)*time.Hour {
1289 t.Errorf("cookie too young %v", session.Age())
1291 assert.Equal(t, startSession.Email, session.Email)
1294func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1295 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1296 opts.Cookie.Expire = time.Duration(24) * time.Hour
1302 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1303 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1304 err = pcTest.SaveSession(startSession)
1305 assert.NoError(t, err)
1307 session, err := pcTest.LoadCookiedSession()
1308 assert.NotEqual(t, nil, err)
1310 t.Errorf("expected nil session %#v", session)
1314func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1315 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1316 opts.Cookie.Expire = time.Duration(24) * time.Hour
1322 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1323 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1324 err = pcTest.SaveSession(startSession)
1325 assert.NoError(t, err)
1327 pcTest.proxy.CookieRefresh = time.Hour
1328 session, err := pcTest.LoadCookiedSession()
1329 assert.NotEqual(t, nil, err)
1331 t.Errorf("expected nil session %#v", session)
1335func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
1336 pcTest, err := NewProcessCookieTestWithDefaults()
1340 pcTest.req, _ = http.NewRequest("GET",
1341 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1345func TestUserInfoEndpointAccepted(t *testing.T) {
1346 test, err := NewUserInfoEndpointTest()
1351 startSession := &sessions.SessionState{
1352 Email: "john.doe@example.com", AccessToken: "my_access_token"}
1353 err = test.SaveSession(startSession)
1354 assert.NoError(t, err)
1356 test.proxy.ServeHTTP(test.rw, test.req)
1357 assert.Equal(t, http.StatusOK, test.rw.Code)
1358 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1359 assert.Equal(t, "{\"email\":\"john.doe@example.com\"}\n", string(bodyBytes))
1362func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1363 test, err := NewUserInfoEndpointTest()
1368 test.proxy.ServeHTTP(test.rw, test.req)
1369 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1372func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1373 pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1377 pcTest.req, _ = http.NewRequest("GET",
1378 pcTest.opts.ProxyPrefix+"/auth", nil)
1382func TestAuthOnlyEndpointAccepted(t *testing.T) {
1383 test, err := NewAuthOnlyEndpointTest()
1388 created := time.Now()
1389 startSession := &sessions.SessionState{
1390 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1391 err = test.SaveSession(startSession)
1392 assert.NoError(t, err)
1394 test.proxy.ServeHTTP(test.rw, test.req)
1395 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1396 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1397 assert.Equal(t, "", string(bodyBytes))
1400func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1401 test, err := NewAuthOnlyEndpointTest()
1406 test.proxy.ServeHTTP(test.rw, test.req)
1407 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1408 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1409 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1412func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1413 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1414 opts.Cookie.Expire = time.Duration(24) * time.Hour
1420 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1421 startSession := &sessions.SessionState{
1422 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1423 err = test.SaveSession(startSession)
1424 assert.NoError(t, err)
1426 test.proxy.ServeHTTP(test.rw, test.req)
1427 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1428 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1429 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1432func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1433 test, err := NewAuthOnlyEndpointTest()
1438 created := time.Now()
1439 startSession := &sessions.SessionState{
1440 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1441 err = test.SaveSession(startSession)
1442 assert.NoError(t, err)
1443 test.validateUser = false
1445 test.proxy.ServeHTTP(test.rw, test.req)
1446 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1447 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1448 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1451func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1452 var pcTest ProcessCookieTest
1454 pcTest.opts = baseTestOptions()
1455 pcTest.opts.SetXAuthRequest = true
1456 err := validation.Validate(pcTest.opts)
1457 assert.NoError(t, err)
1459 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1460 return pcTest.validateUser
1465 pcTest.proxy.provider = &TestProvider{
1469 pcTest.validateUser = true
1471 pcTest.rw = httptest.NewRecorder()
1472 pcTest.req, _ = http.NewRequest("GET",
1473 pcTest.opts.ProxyPrefix+"/auth", nil)
1475 created := time.Now()
1476 startSession := &sessions.SessionState{
1477 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1478 err = pcTest.SaveSession(startSession)
1479 assert.NoError(t, err)
1481 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1482 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1483 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1484 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1487func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1488 var pcTest ProcessCookieTest
1490 pcTest.opts = baseTestOptions()
1491 pcTest.opts.SetXAuthRequest = true
1492 pcTest.opts.SetBasicAuth = true
1493 err := validation.Validate(pcTest.opts)
1494 assert.NoError(t, err)
1496 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1497 return pcTest.validateUser
1502 pcTest.proxy.provider = &TestProvider{
1506 pcTest.validateUser = true
1508 pcTest.rw = httptest.NewRecorder()
1509 pcTest.req, _ = http.NewRequest("GET",
1510 pcTest.opts.ProxyPrefix+"/auth", nil)
1512 created := time.Now()
1513 startSession := &sessions.SessionState{
1514 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1515 err = pcTest.SaveSession(startSession)
1516 assert.NoError(t, err)
1518 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1519 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1520 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1521 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1522 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:"+pcTest.opts.BasicAuthPassword))
1523 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1526func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1527 var pcTest ProcessCookieTest
1529 pcTest.opts = baseTestOptions()
1530 pcTest.opts.SetXAuthRequest = true
1531 pcTest.opts.SetBasicAuth = false
1532 err := validation.Validate(pcTest.opts)
1533 assert.NoError(t, err)
1535 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1536 return pcTest.validateUser
1541 pcTest.proxy.provider = &TestProvider{
1545 pcTest.validateUser = true
1547 pcTest.rw = httptest.NewRecorder()
1548 pcTest.req, _ = http.NewRequest("GET",
1549 pcTest.opts.ProxyPrefix+"/auth", nil)
1551 created := time.Now()
1552 startSession := &sessions.SessionState{
1553 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1554 err = pcTest.SaveSession(startSession)
1555 assert.NoError(t, err)
1557 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1558 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1559 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1560 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1561 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1564func TestAuthSkippedForPreflightRequests(t *testing.T) {
1565 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1567 _, err := w.Write([]byte("response"))
1572 t.Cleanup(upstream.Close)
1574 opts := baseTestOptions()
1575 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1576 opts.SkipAuthPreflight = true
1577 err := validation.Validate(opts)
1578 assert.NoError(t, err)
1580 upstreamURL, _ := url.Parse(upstream.URL)
1581 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1583 proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
1587 rw := httptest.NewRecorder()
1588 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1589 proxy.ServeHTTP(rw, req)
1591 assert.Equal(t, 200, rw.Code)
1592 assert.Equal(t, "response", rw.Body.String())
1595type SignatureAuthenticator struct {
1596 auth hmacauth.HmacAuth
1599func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1600 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1604 case hmacauth.ResultNoSignature:
1605 msg = "no signature received"
1606 case hmacauth.ResultMatch:
1607 msg = "signatures match"
1608 case hmacauth.ResultMismatch:
1610 "signatures do not match:\n received: %s\n computed: %s",
1614 panic("unknown result value: " + result.String())
1617 _, err := w.Write([]byte(msg))
1623type SignatureTest struct {
1624 opts *options.Options
1625 upstream *httptest.Server
1627 provider *httptest.Server
1629 rw *httptest.ResponseRecorder
1630 authenticator *SignatureAuthenticator
1633func NewSignatureTest() (*SignatureTest, error) {
1634 opts := baseTestOptions()
1635 opts.EmailDomains = []string{"acm.org"}
1637 authenticator := &SignatureAuthenticator{}
1638 upstream := httptest.NewServer(
1639 http.HandlerFunc(authenticator.Authenticate))
1640 upstreamURL, err := url.Parse(upstream.URL)
1644 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1646 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1647 _, err := w.Write([]byte(`{"access_token": "my_auth_token"}`))
1652 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1653 providerURL, err := url.Parse(provider.URL)
1657 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1659 return &SignatureTest{
1665 httptest.NewRecorder(),
1670func (st *SignatureTest) Close() {
1675// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1676// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1677// https://github.com/18F/hmacauth/pull/4
1678type fakeNetConn struct {
1682func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1683 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1684 copy(p, fnc.reqBody)
1686 return bodyLen, io.EOF
1691func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) error {
1692 err := validation.Validate(st.opts)
1696 proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
1701 var bodyBuf io.ReadCloser
1703 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1705 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1706 req.Header = st.header
1708 state := &sessions.SessionState{
1709 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1710 err = proxy.SaveSession(st.rw, req, state)
1714 for _, c := range st.rw.Result().Cookies() {
1717 // This is used by the upstream to validate the signature.
1718 st.authenticator.auth = hmacauth.NewHmacAuth(
1719 crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
1720 proxy.ServeHTTP(st.rw, req)
1725func TestRequestSignature(t *testing.T) {
1726 testCases := map[string]struct {
1732 "No request signature": {
1736 resp: "no signature received",
1741 key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1742 resp: "signatures match",
1746 body: `{ "hello": "world!" }`,
1747 key: "d90df39e2d19282840252612dd7c81421a372f61",
1748 resp: "signatures match",
1751 for name, tc := range testCases {
1752 t.Run(name, func(t *testing.T) {
1753 st, err := NewSignatureTest()
1759 st.opts.SignatureKey = fmt.Sprintf("sha1:%s", tc.key)
1761 err = st.MakeRequestWithExpectedKey(tc.method, tc.body, tc.key)
1762 assert.NoError(t, err)
1763 assert.Equal(t, 200, st.rw.Code)
1764 assert.Equal(t, tc.resp, st.rw.Body.String())
1769func TestGetRedirect(t *testing.T) {
1770 opts := baseTestOptions()
1771 err := validation.Validate(opts)
1772 assert.NoError(t, err)
1773 require.NotEmpty(t, opts.ProxyPrefix)
1774 proxy, err := NewOAuthProxy(opts, func(s string) bool { return false })
1782 expectedRedirect string
1785 name: "request outside of ProxyPrefix redirects to original URL",
1787 expectedRedirect: "/foo/bar",
1790 name: "request under ProxyPrefix redirects to root",
1791 url: proxy.ProxyPrefix + "/foo/bar",
1792 expectedRedirect: "/",
1795 for _, tt := range tests {
1796 t.Run(tt.name, func(t *testing.T) {
1797 req, _ := http.NewRequest("GET", tt.url, nil)
1798 redirect, err := proxy.GetRedirect(req)
1800 assert.NoError(t, err)
1801 assert.Equal(t, tt.expectedRedirect, redirect)
1806type ajaxRequestTest struct {
1807 opts *options.Options
1811func newAjaxRequestTest() (*ajaxRequestTest, error) {
1812 test := &ajaxRequestTest{}
1813 test.opts = baseTestOptions()
1814 err := validation.Validate(test.opts)
1819 test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
1828func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1829 rw := httptest.NewRecorder()
1830 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1835 test.proxy.ServeHTTP(rw, req)
1836 return rw.Code, rw.Header(), nil
1839func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1840 test, err := newAjaxRequestTest()
1846 code, rh, err := test.getEndpoint(endpoint, header)
1847 assert.NoError(t, err)
1848 assert.Equal(t, http.StatusUnauthorized, code)
1849 mime := rh.Get("Content-Type")
1850 assert.Equal(t, applicationJSON, mime)
1852func TestAjaxUnauthorizedRequest1(t *testing.T) {
1853 header := make(http.Header)
1854 header.Add("accept", applicationJSON)
1856 testAjaxUnauthorizedRequest(t, header)
1859func TestAjaxUnauthorizedRequest2(t *testing.T) {
1860 header := make(http.Header)
1861 header.Add("Accept", applicationJSON)
1863 testAjaxUnauthorizedRequest(t, header)
1866func TestAjaxForbiddendRequest(t *testing.T) {
1867 test, err := newAjaxRequestTest()
1872 header := make(http.Header)
1873 code, rh, err := test.getEndpoint(endpoint, header)
1874 assert.NoError(t, err)
1875 assert.Equal(t, http.StatusForbidden, code)
1876 mime := rh.Get("Content-Type")
1877 assert.NotEqual(t, applicationJSON, mime)
1880func TestClearSplitCookie(t *testing.T) {
1881 opts := baseTestOptions()
1882 opts.Cookie.Secret = base64CookieSecret
1883 opts.Cookie.Name = "oauth2"
1884 opts.Cookie.Domains = []string{"abc"}
1885 err := validation.Validate(opts)
1886 assert.NoError(t, err)
1888 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1893 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1894 var rw = httptest.NewRecorder()
1895 req := httptest.NewRequest("get", "/", nil)
1897 req.AddCookie(&http.Cookie{
1901 req.AddCookie(&http.Cookie{
1905 req.AddCookie(&http.Cookie{
1910 err = p.ClearSessionCookie(rw, req)
1911 assert.NoError(t, err)
1912 header := rw.Header()
1914 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1917func TestClearSingleCookie(t *testing.T) {
1918 opts := baseTestOptions()
1919 opts.Cookie.Name = "oauth2"
1920 opts.Cookie.Domains = []string{"abc"}
1921 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1926 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1927 var rw = httptest.NewRecorder()
1928 req := httptest.NewRequest("get", "/", nil)
1930 req.AddCookie(&http.Cookie{
1934 req.AddCookie(&http.Cookie{
1939 err = p.ClearSessionCookie(rw, req)
1940 assert.NoError(t, err)
1941 header := rw.Header()
1943 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1946type NoOpKeySet struct {
1949func (NoOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
1950 splitStrings := strings.Split(jwt, ".")
1951 payloadString := splitStrings[1]
1952 return base64.RawURLEncoding.DecodeString(payloadString)
1955func TestGetJwtSession(t *testing.T) {
1958 "sub": "1234567890",
1959 "aud": "https://test.myapp.com",
1961 "email": "john@example.com",
1962 "iss": "https://issuer.example.com",
1967 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1968 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1969 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1970 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1971 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1972 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1974 keyset := NoOpKeySet{}
1975 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1976 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1978 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1979 opts.PassAuthorization = true
1980 opts.SetAuthorization = true
1981 opts.SetXAuthRequest = true
1982 opts.SkipJwtBearerTokens = true
1983 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
1988 tp, _ := test.proxy.provider.(*TestProvider)
1989 tp.GroupValidator = func(s string) bool {
1993 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1994 test.req.Header = map[string][]string{
1995 "Authorization": {authHeader},
1999 expires := time.Unix(1912151821, 0)
2000 session, err := test.proxy.GetJwtSession(test.req)
2001 assert.NoError(t, err)
2002 assert.Equal(t, session.User, "1234567890")
2003 assert.Equal(t, session.Email, "john@example.com")
2004 assert.Equal(t, session.ExpiresOn, &expires)
2005 assert.Equal(t, session.IDToken, goodJwt)
2007 test.proxy.ServeHTTP(test.rw, test.req)
2008 if test.rw.Code >= 400 {
2009 t.Fatalf("expected 3xx got %d", test.rw.Code)
2012 // Check PassAuthorization, should overwrite Basic header
2013 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
2014 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "1234567890")
2015 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
2017 // SetAuthorization and SetXAuthRequest
2018 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
2019 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "1234567890")
2020 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
2023func TestFindJwtBearerToken(t *testing.T) {
2024 p := OAuthProxy{CookieName: "oauth2", CookieDomains: []string{"abc"}}
2025 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}}
2027 validToken := "eyJfoobar.eyJfoobar.12345asdf"
2031 getReq.Header = map[string][]string{
2032 "Authorization": {fmt.Sprintf("Bearer %s", validToken)},
2035 token, err := p.findBearerToken(getReq)
2036 assert.NoError(t, err)
2037 assert.Equal(t, validToken, token)
2039 // Basic - no password
2040 getReq.SetBasicAuth(token, "")
2041 token, err = p.findBearerToken(getReq)
2042 assert.NoError(t, err)
2043 assert.Equal(t, validToken, token)
2045 // Basic - sentinel password
2046 getReq.SetBasicAuth(token, "x-oauth-basic")
2047 token, err = p.findBearerToken(getReq)
2048 assert.NoError(t, err)
2049 assert.Equal(t, validToken, token)
2051 // Basic - any username, password matching jwt pattern
2052 getReq.SetBasicAuth("any-username-you-could-wish-for", token)
2053 token, err = p.findBearerToken(getReq)
2054 assert.NoError(t, err)
2055 assert.Equal(t, validToken, token)
2057 failures := []string{
2059 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
2061 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA",
2062 // Invalid encrypted key
2063 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.//////.dGVzdA.dGVzdA.dGVzdA",
2065 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.//////.dGVzdA.dGVzdA",
2066 // Invalid ciphertext
2067 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.//////.dGVzdA",
2069 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.//////",
2071 "W10.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
2073 "######.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
2074 // Missing alc/enc params
2075 "e30.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
2078 for _, failure := range failures {
2079 getReq.Header = map[string][]string{
2080 "Authorization": {fmt.Sprintf("Bearer %s", failure)},
2082 _, err := p.findBearerToken(getReq)
2083 assert.Error(t, err)
2087func Test_prepareNoCache(t *testing.T) {
2088 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2091 mux := http.NewServeMux()
2092 mux.Handle("/", handler)
2094 rec := httptest.NewRecorder()
2095 req := httptest.NewRequest(http.MethodGet, "/", nil)
2096 mux.ServeHTTP(rec, req)
2098 for k, v := range noCacheHeaders {
2099 assert.Equal(t, rec.Header().Get(k), v)
2103func Test_noCacheHeaders(t *testing.T) {
2104 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2105 _, err := w.Write([]byte("upstream"))
2110 t.Cleanup(upstream.Close)
2112 opts := baseTestOptions()
2113 opts.Upstreams = []string{upstream.URL}
2114 opts.SkipAuthRegex = []string{".*"}
2115 err := validation.Validate(opts)
2116 assert.NoError(t, err)
2117 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2122 t.Run("not exist in response from upstream", func(t *testing.T) {
2123 rec := httptest.NewRecorder()
2124 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
2125 proxy.ServeHTTP(rec, req)
2127 assert.Equal(t, http.StatusOK, rec.Code)
2128 assert.Equal(t, "upstream", rec.Body.String())
2130 // checking noCacheHeaders does not exists in response headers from upstream
2131 for k := range noCacheHeaders {
2132 assert.Equal(t, "", rec.Header().Get(k))
2136 t.Run("has no-cache", func(t *testing.T) {
2142 path: "/oauth2/sign_in",
2146 path: "/oauth2/sign_out",
2150 path: "/oauth2/start",
2154 path: "/oauth2/callback",
2158 path: "/oauth2/auth",
2162 path: "/oauth2/userinfo",
2171 for _, tt := range tests {
2172 t.Run(tt.path, func(t *testing.T) {
2173 rec := httptest.NewRecorder()
2174 req := httptest.NewRequest(http.MethodGet, tt.path, nil)
2175 proxy.ServeHTTP(rec, req)
2176 cacheControl := rec.Result().Header.Get("Cache-Control")
2177 if tt.hasNoCache != (strings.Contains(cacheControl, "no-cache")) {
2178 t.Errorf(`unexpected "Cache-Control" header: %s`, cacheControl)
2186func baseTestOptions() *options.Options {
2187 opts := options.NewOptions()
2188 opts.Cookie.Secret = rawCookieSecret
2189 opts.ClientID = clientID
2190 opts.ClientSecret = clientSecret
2191 opts.EmailDomains = []string{"*"}
2195func TestTrustedIPs(t *testing.T) {
2200 realClientIPHeader string
2204 // Check unconfigured behavior.
2208 reverseProxy: false,
2209 realClientIPHeader: "X-Real-IP", // Default value
2210 req: func() *http.Request {
2211 req, _ := http.NewRequest("GET", "/", nil)
2214 expectTrusted: false,
2216 // Check using req.RemoteAddr (Options.ReverseProxy == false).
2218 name: "WithRemoteAddr",
2219 trustedIPs: []string{"127.0.0.1"},
2220 reverseProxy: false,
2221 realClientIPHeader: "X-Real-IP", // Default value
2222 req: func() *http.Request {
2223 req, _ := http.NewRequest("GET", "/", nil)
2224 req.RemoteAddr = "127.0.0.1:43670"
2227 expectTrusted: true,
2229 // Check ignores req.RemoteAddr match when behind a reverse proxy / missing header.
2231 name: "IgnoresRemoteAddrInReverseProxyMode",
2232 trustedIPs: []string{"127.0.0.1"},
2234 realClientIPHeader: "X-Real-IP", // Default value
2235 req: func() *http.Request {
2236 req, _ := http.NewRequest("GET", "/", nil)
2237 req.RemoteAddr = "127.0.0.1:44324"
2240 expectTrusted: false,
2242 // Check successful trusting of localhost in IPv4.
2244 name: "TrustsLocalhostInReverseProxyMode",
2245 trustedIPs: []string{"127.0.0.0/8", "::1"},
2247 realClientIPHeader: "X-Forwarded-For",
2248 req: func() *http.Request {
2249 req, _ := http.NewRequest("GET", "/", nil)
2250 req.Header.Add("X-Forwarded-For", "127.0.0.1")
2253 expectTrusted: true,
2255 // Check successful trusting of localhost in IPv6.
2257 name: "TrustsIP6LocalostInReverseProxyMode",
2258 trustedIPs: []string{"127.0.0.0/8", "::1"},
2260 realClientIPHeader: "X-Forwarded-For",
2261 req: func() *http.Request {
2262 req, _ := http.NewRequest("GET", "/", nil)
2263 req.Header.Add("X-Forwarded-For", "::1")
2266 expectTrusted: true,
2268 // Check does not trust random IPv4 address.
2270 name: "DoesNotTrustRandomIP4Address",
2271 trustedIPs: []string{"127.0.0.0/8", "::1"},
2273 realClientIPHeader: "X-Forwarded-For",
2274 req: func() *http.Request {
2275 req, _ := http.NewRequest("GET", "/", nil)
2276 req.Header.Add("X-Forwarded-For", "12.34.56.78")
2279 expectTrusted: false,
2281 // Check does not trust random IPv6 address.
2283 name: "DoesNotTrustRandomIP6Address",
2284 trustedIPs: []string{"127.0.0.0/8", "::1"},
2286 realClientIPHeader: "X-Forwarded-For",
2287 req: func() *http.Request {
2288 req, _ := http.NewRequest("GET", "/", nil)
2289 req.Header.Add("X-Forwarded-For", "::2")
2292 expectTrusted: false,
2294 // Check respects correct header.
2296 name: "RespectsCorrectHeaderInReverseProxyMode",
2297 trustedIPs: []string{"127.0.0.0/8", "::1"},
2299 realClientIPHeader: "X-Forwarded-For",
2300 req: func() *http.Request {
2301 req, _ := http.NewRequest("GET", "/", nil)
2302 req.Header.Add("X-Real-IP", "::1")
2305 expectTrusted: false,
2307 // Check doesn't trust if garbage is provided.
2309 name: "DoesNotTrustGarbageInReverseProxyMode",
2310 trustedIPs: []string{"127.0.0.0/8", "::1"},
2312 realClientIPHeader: "X-Forwarded-For",
2313 req: func() *http.Request {
2314 req, _ := http.NewRequest("GET", "/", nil)
2315 req.Header.Add("X-Forwarded-For", "adsfljk29242as!!")
2318 expectTrusted: false,
2320 // Check doesn't trust if garbage is provided (no reverse-proxy).
2322 name: "DoesNotTrustGarbage",
2323 trustedIPs: []string{"127.0.0.0/8", "::1"},
2324 reverseProxy: false,
2325 realClientIPHeader: "X-Real-IP",
2326 req: func() *http.Request {
2327 req, _ := http.NewRequest("GET", "/", nil)
2328 req.RemoteAddr = "adsfljk29242as!!"
2331 expectTrusted: false,
2335 for _, tt := range tests {
2336 t.Run(tt.name, func(t *testing.T) {
2337 opts := baseTestOptions()
2338 opts.Upstreams = []string{"static://200"}
2339 opts.TrustedIPs = tt.trustedIPs
2340 opts.ReverseProxy = tt.reverseProxy
2341 opts.RealClientIPHeader = tt.realClientIPHeader
2342 validation.Validate(opts)
2344 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
2345 assert.NoError(t, err)
2346 rw := httptest.NewRecorder()
2348 proxy.ServeHTTP(rw, tt.req)
2349 if tt.expectTrusted {
2350 assert.Equal(t, 200, rw.Code)
2352 assert.Equal(t, 403, 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