git.iko.soy/typeable/oauth2-proxy
on v5.1.1 help sign in
typeable/oauth2-proxy/oauthproxy_test.go
1581 lines 46 kB history raw
1package main
3import (
4 "context"
5 "crypto"
6 "encoding/base64"
7 "fmt"
8 "io"
9 "io/ioutil"
10 "net"
11 "net/http"
12 "net/http/httptest"
13 "net/url"
14 "regexp"
15 "strings"
16 "testing"
17 "time"
19 "github.com/coreos/go-oidc"
20 "github.com/mbland/hmacauth"
21 "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
22 "github.com/pusher/oauth2_proxy/pkg/logger"
23 "github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
24 "github.com/pusher/oauth2_proxy/providers"
25 "github.com/stretchr/testify/assert"
26 "github.com/stretchr/testify/require"
27 "golang.org/x/net/websocket"
30func init() {
31 logger.SetFlags(logger.Lshortfile)
35type WebSocketOrRestHandler struct {
36 restHandler http.Handler
37 wsHandler http.Handler
40func (h *WebSocketOrRestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
41 if r.Header.Get("Upgrade") == "websocket" {
42 h.wsHandler.ServeHTTP(w, r)
43 } else {
44 h.restHandler.ServeHTTP(w, r)
45 }
48func TestWebSocketProxy(t *testing.T) {
49 handler := WebSocketOrRestHandler{
50 restHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
51 w.WriteHeader(200)
52 hostname, _, _ := net.SplitHostPort(r.Host)
53 w.Write([]byte(hostname))
54 }),
55 wsHandler: websocket.Handler(func(ws *websocket.Conn) {
56 defer ws.Close()
57 var data []byte
58 err := websocket.Message.Receive(ws, &data)
59 if err != nil {
60 t.Fatalf("err %s", err)
61 return
62 }
63 err = websocket.Message.Send(ws, data)
64 if err != nil {
65 t.Fatalf("err %s", err)
66 }
67 return
68 }),
69 }
70 backend := httptest.NewServer(&handler)
71 defer backend.Close()
73 backendURL, _ := url.Parse(backend.URL)
75 options := NewOptions()
76 var auth hmacauth.HmacAuth
77 options.PassHostHeader = true
78 proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, options, auth)
79 frontend := httptest.NewServer(proxyHandler)
80 defer frontend.Close()
82 frontendURL, _ := url.Parse(frontend.URL)
83 frontendWSURL := "ws://" + frontendURL.Host + "/"
85 ws, err := websocket.Dial(frontendWSURL, "", "http://localhost/")
86 if err != nil {
87 t.Fatalf("err %s", err)
88 }
89 request := []byte("hello, world!")
90 err = websocket.Message.Send(ws, request)
91 if err != nil {
92 t.Fatalf("err %s", err)
93 }
94 var response = make([]byte, 1024)
95 websocket.Message.Receive(ws, &response)
96 if err != nil {
97 t.Fatalf("err %s", err)
98 }
99 if g, e := string(request), string(response); g != e {
100 t.Errorf("got body %q; expected %q", g, e)
101 }
103 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
104 res, _ := http.DefaultClient.Do(getReq)
105 bodyBytes, _ := ioutil.ReadAll(res.Body)
106 backendHostname, _, _ := net.SplitHostPort(backendURL.Host)
107 if g, e := string(bodyBytes), backendHostname; g != e {
108 t.Errorf("got body %q; expected %q", g, e)
109 }
112func TestNewReverseProxy(t *testing.T) {
113 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
114 w.WriteHeader(200)
115 hostname, _, _ := net.SplitHostPort(r.Host)
116 w.Write([]byte(hostname))
117 }))
118 defer backend.Close()
120 backendURL, _ := url.Parse(backend.URL)
121 backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host)
122 backendHost := net.JoinHostPort(backendHostname, backendPort)
123 proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
125 proxyHandler := NewReverseProxy(proxyURL, &Options{FlushInterval: time.Second})
126 setProxyUpstreamHostHeader(proxyHandler, proxyURL)
127 frontend := httptest.NewServer(proxyHandler)
128 defer frontend.Close()
130 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
131 res, _ := http.DefaultClient.Do(getReq)
132 bodyBytes, _ := ioutil.ReadAll(res.Body)
133 if g, e := string(bodyBytes), backendHostname; g != e {
134 t.Errorf("got body %q; expected %q", g, e)
135 }
138func TestEncodedSlashes(t *testing.T) {
139 var seen string
140 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
141 w.WriteHeader(200)
142 seen = r.RequestURI
143 }))
144 defer backend.Close()
146 b, _ := url.Parse(backend.URL)
147 proxyHandler := NewReverseProxy(b, &Options{FlushInterval: time.Second})
148 setProxyDirector(proxyHandler)
149 frontend := httptest.NewServer(proxyHandler)
150 defer frontend.Close()
152 f, _ := url.Parse(frontend.URL)
153 encodedPath := "/a%2Fb/?c=1"
154 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: f.Host, Opaque: encodedPath}}
155 _, err := http.DefaultClient.Do(getReq)
156 if err != nil {
157 t.Fatalf("err %s", err)
158 }
159 if seen != encodedPath {
160 t.Errorf("got bad request %q expected %q", seen, encodedPath)
161 }
164func TestRobotsTxt(t *testing.T) {
165 opts := NewOptions()
166 opts.ClientID = "asdlkjx"
167 opts.ClientSecret = "alkgks"
168 opts.CookieSecret = "asdkugkj"
169 opts.Validate()
171 proxy := NewOAuthProxy(opts, func(string) bool { return true })
172 rw := httptest.NewRecorder()
173 req, _ := http.NewRequest("GET", "/robots.txt", nil)
174 proxy.ServeHTTP(rw, req)
175 assert.Equal(t, 200, rw.Code)
176 assert.Equal(t, "User-agent: *\nDisallow: /", rw.Body.String())
179func TestIsValidRedirect(t *testing.T) {
180 opts := NewOptions()
181 opts.ClientID = "skdlfj"
182 opts.ClientSecret = "fgkdsgj"
183 opts.CookieSecret = "ljgiogbj"
184 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
185 opts.WhitelistDomains = []string{
186 "foo.bar",
187 ".bar.foo",
188 "port.bar:8080",
189 ".sub.port.bar:8080",
190 "anyport.bar:*",
191 ".sub.anyport.bar:*",
192 }
193 opts.Validate()
195 proxy := NewOAuthProxy(opts, func(string) bool { return true })
197 testCases := []struct {
198 Desc, Redirect string
199 ExpectedResult bool
200 }{
201 {
202 Desc: "noRD",
203 Redirect: "",
204 ExpectedResult: false,
205 },
206 {
207 Desc: "singleSlash",
208 Redirect: "/redirect",
209 ExpectedResult: true,
210 },
211 {
212 Desc: "doubleSlash",
213 Redirect: "//redirect",
214 ExpectedResult: false,
215 },
216 {
217 Desc: "validHTTP",
218 Redirect: "http://foo.bar/redirect",
219 ExpectedResult: true,
220 },
221 {
222 Desc: "validHTTPS",
223 Redirect: "https://foo.bar/redirect",
224 ExpectedResult: true,
225 },
226 {
227 Desc: "invalidHTTPSubdomain",
228 Redirect: "http://baz.foo.bar/redirect",
229 ExpectedResult: false,
230 },
231 {
232 Desc: "invalidHTTPSSubdomain",
233 Redirect: "https://baz.foo.bar/redirect",
234 ExpectedResult: false,
235 },
236 {
237 Desc: "validHTTPSubdomain",
238 Redirect: "http://baz.bar.foo/redirect",
239 ExpectedResult: true,
240 },
241 {
242 Desc: "validHTTPSSubdomain",
243 Redirect: "https://baz.bar.foo/redirect",
244 ExpectedResult: true,
245 },
246 {
247 Desc: "validHTTPDomain",
248 Redirect: "http://bar.foo/redirect",
249 ExpectedResult: true,
250 },
251 {
252 Desc: "invalidHTTP1",
253 Redirect: "http://foo.bar.evil.corp/redirect",
254 ExpectedResult: false,
255 },
256 {
257 Desc: "invalidHTTPS1",
258 Redirect: "https://foo.bar.evil.corp/redirect",
259 ExpectedResult: false,
260 },
261 {
262 Desc: "invalidHTTP2",
263 Redirect: "http://evil.corp/redirect?rd=foo.bar",
264 ExpectedResult: false,
265 },
266 {
267 Desc: "invalidHTTPS2",
268 Redirect: "https://evil.corp/redirect?rd=foo.bar",
269 ExpectedResult: false,
270 },
271 {
272 Desc: "invalidPort",
273 Redirect: "https://evil.corp:3838/redirect",
274 ExpectedResult: false,
275 },
276 {
277 Desc: "invalidEmptyPort",
278 Redirect: "http://foo.bar:3838/redirect",
279 ExpectedResult: false,
280 },
281 {
282 Desc: "invalidEmptyPortSubdomain",
283 Redirect: "http://baz.bar.foo:3838/redirect",
284 ExpectedResult: false,
285 },
286 {
287 Desc: "validSpecificPort",
288 Redirect: "http://port.bar:8080/redirect",
289 ExpectedResult: true,
290 },
291 {
292 Desc: "invalidSpecificPort",
293 Redirect: "http://port.bar:3838/redirect",
294 ExpectedResult: false,
295 },
296 {
297 Desc: "validSpecificPortSubdomain",
298 Redirect: "http://foo.sub.port.bar:8080/redirect",
299 ExpectedResult: true,
300 },
301 {
302 Desc: "invalidSpecificPortSubdomain",
303 Redirect: "http://foo.sub.port.bar:3838/redirect",
304 ExpectedResult: false,
305 },
306 {
307 Desc: "validAnyPort1",
308 Redirect: "http://anyport.bar:8080/redirect",
309 ExpectedResult: true,
310 },
311 {
312 Desc: "validAnyPort2",
313 Redirect: "http://anyport.bar:8081/redirect",
314 ExpectedResult: true,
315 },
316 {
317 Desc: "validAnyPortSubdomain1",
318 Redirect: "http://a.sub.anyport.bar:8080/redirect",
319 ExpectedResult: true,
320 },
321 {
322 Desc: "validAnyPortSubdomain2",
323 Redirect: "http://a.sub.anyport.bar:8081/redirect",
324 ExpectedResult: true,
325 },
326 {
327 Desc: "openRedirect1",
328 Redirect: "/\\evil.com",
329 ExpectedResult: false,
330 },
331 {
332 Desc: "openRedirectSpace1",
333 Redirect: "/ /evil.com",
334 ExpectedResult: false,
335 },
336 {
337 Desc: "openRedirectSpace2",
338 Redirect: "/ \\evil.com",
339 ExpectedResult: false,
340 },
341 {
342 Desc: "openRedirectTab1",
343 Redirect: "/\t/evil.com",
344 ExpectedResult: false,
345 },
346 {
347 Desc: "openRedirectTab2",
348 Redirect: "/\t\\evil.com",
349 ExpectedResult: false,
350 },
351 {
352 Desc: "openRedirectVerticalTab1",
353 Redirect: "/\v/evil.com",
354 ExpectedResult: false,
355 },
356 {
357 Desc: "openRedirectVerticalTab2",
358 Redirect: "/\v\\evil.com",
359 ExpectedResult: false,
360 },
361 {
362 Desc: "openRedirectNewLine1",
363 Redirect: "/\n/evil.com",
364 ExpectedResult: false,
365 },
366 {
367 Desc: "openRedirectNewLine2",
368 Redirect: "/\n\\evil.com",
369 ExpectedResult: false,
370 },
371 {
372 Desc: "openRedirectCarriageReturn1",
373 Redirect: "/\r/evil.com",
374 ExpectedResult: false,
375 },
376 {
377 Desc: "openRedirectCarriageReturn2",
378 Redirect: "/\r\\evil.com",
379 ExpectedResult: false,
380 },
381 }
383 for _, tc := range testCases {
384 t.Run(tc.Desc, func(t *testing.T) {
385 result := proxy.IsValidRedirect(tc.Redirect)
387 if result != tc.ExpectedResult {
388 t.Errorf("expected %t got %t", tc.ExpectedResult, result)
389 }
390 })
391 }
394type TestProvider struct {
395 *providers.ProviderData
396 EmailAddress string
397 ValidToken bool
398 GroupValidator func(string) bool
401func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
402 return &TestProvider{
403 ProviderData: &providers.ProviderData{
404 ProviderName: "Test Provider",
405 LoginURL: &url.URL{
406 Scheme: "http",
407 Host: providerURL.Host,
408 Path: "/oauth/authorize",
409 },
410 RedeemURL: &url.URL{
411 Scheme: "http",
412 Host: providerURL.Host,
413 Path: "/oauth/token",
414 },
415 ProfileURL: &url.URL{
416 Scheme: "http",
417 Host: providerURL.Host,
418 Path: "/api/v1/profile",
419 },
420 Scope: "profile.email",
421 },
422 EmailAddress: emailAddress,
423 GroupValidator: func(s string) bool {
424 return true
425 },
426 }
429func (tp *TestProvider) GetEmailAddress(session *sessions.SessionState) (string, error) {
430 return tp.EmailAddress, nil
433func (tp *TestProvider) ValidateSessionState(session *sessions.SessionState) bool {
434 return tp.ValidToken
437func TestBasicAuthPassword(t *testing.T) {
438 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
439 logger.Printf("%#v", r)
440 var payload string
441 switch r.URL.Path {
442 case "/oauth/token":
443 payload = `{"access_token": "my_auth_token"}`
444 default:
445 payload = r.Header.Get("Authorization")
446 if payload == "" {
447 payload = "No Authorization header found."
448 }
449 }
450 w.WriteHeader(200)
451 w.Write([]byte(payload))
452 }))
453 opts := NewOptions()
454 opts.Upstreams = append(opts.Upstreams, providerServer.URL)
455 // The CookieSecret must be 32 bytes in order to create the AES
456 // cipher.
457 opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
458 opts.ClientID = "dlgkj"
459 opts.ClientSecret = "alkgret"
460 opts.CookieSecure = false
461 opts.PassBasicAuth = true
462 opts.PassUserHeaders = true
463 opts.PreferEmailToUser = true
464 opts.BasicAuthPassword = "This is a secure password"
465 opts.Validate()
467 providerURL, _ := url.Parse(providerServer.URL)
468 const emailAddress = "john.doe@example.com"
470 opts.provider = NewTestProvider(providerURL, emailAddress)
471 proxy := NewOAuthProxy(opts, func(email string) bool {
472 return email == emailAddress
473 })
475 rw := httptest.NewRecorder()
476 req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
477 strings.NewReader(""))
478 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
479 proxy.ServeHTTP(rw, req)
480 if rw.Code >= 400 {
481 t.Fatalf("expected 3xx got %d", rw.Code)
482 }
483 cookie := rw.HeaderMap["Set-Cookie"][1]
485 cookieName := proxy.CookieName
486 var value string
487 keyPrefix := cookieName + "="
489 for _, field := range strings.Split(cookie, "; ") {
490 value = strings.TrimPrefix(field, keyPrefix)
491 if value != field {
492 break
493 } else {
494 value = ""
495 }
496 }
498 req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
499 req.AddCookie(&http.Cookie{
500 Name: cookieName,
501 Value: value,
502 Path: "/",
503 Expires: time.Now().Add(time.Duration(24)),
504 HttpOnly: true,
505 })
506 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
508 rw = httptest.NewRecorder()
509 proxy.ServeHTTP(rw, req)
511 // The username in the basic auth credentials is expected to be equal to the email address from the
512 // auth response, so we use the same variable here.
513 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
514 assert.Equal(t, expectedHeader, rw.Body.String())
515 providerServer.Close()
518func TestBasicAuthWithEmail(t *testing.T) {
519 opts := NewOptions()
520 opts.PassBasicAuth = true
521 opts.PassUserHeaders = false
522 opts.PreferEmailToUser = false
523 opts.BasicAuthPassword = "This is a secure password"
524 opts.Validate()
526 const emailAddress = "john.doe@example.com"
527 const userName = "9fcab5c9b889a557"
529 // The username in the basic auth credentials is expected to be equal to the email address from the
530 expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
531 expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
533 session := &sessions.SessionState{
534 User: userName,
535 Email: emailAddress,
536 AccessToken: "oauth_token",
537 CreatedAt: time.Now(),
538 }
539 {
540 rw := httptest.NewRecorder()
541 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
542 proxy := NewOAuthProxy(opts, func(email string) bool {
543 return email == emailAddress
544 })
545 proxy.addHeadersForProxying(rw, req, session)
546 assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
547 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
548 }
550 opts.PreferEmailToUser = true
551 {
552 rw := httptest.NewRecorder()
553 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
555 proxy := NewOAuthProxy(opts, func(email string) bool {
556 return email == emailAddress
557 })
558 proxy.addHeadersForProxying(rw, req, session)
559 assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
560 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
561 }
564func TestPassUserHeadersWithEmail(t *testing.T) {
565 opts := NewOptions()
566 opts.PassBasicAuth = false
567 opts.PassUserHeaders = true
568 opts.PreferEmailToUser = false
569 opts.Validate()
571 const emailAddress = "john.doe@example.com"
572 const userName = "9fcab5c9b889a557"
574 session := &sessions.SessionState{
575 User: userName,
576 Email: emailAddress,
577 AccessToken: "oauth_token",
578 CreatedAt: time.Now(),
579 }
580 {
581 rw := httptest.NewRecorder()
582 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
583 proxy := NewOAuthProxy(opts, func(email string) bool {
584 return email == emailAddress
585 })
586 proxy.addHeadersForProxying(rw, req, session)
587 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
588 }
590 opts.PreferEmailToUser = true
591 {
592 rw := httptest.NewRecorder()
593 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
595 proxy := NewOAuthProxy(opts, func(email string) bool {
596 return email == emailAddress
597 })
598 proxy.addHeadersForProxying(rw, req, session)
599 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
600 }
603type PassAccessTokenTest struct {
604 providerServer *httptest.Server
605 proxy *OAuthProxy
606 opts *Options
609type PassAccessTokenTestOptions struct {
610 PassAccessToken bool
611 ProxyUpstream string
614func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
615 t := &PassAccessTokenTest{}
617 t.providerServer = httptest.NewServer(
618 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
619 var payload string
620 switch r.URL.Path {
621 case "/oauth/token":
622 payload = `{"access_token": "my_auth_token"}`
623 default:
624 payload = r.Header.Get("X-Forwarded-Access-Token")
625 if payload == "" {
626 payload = "No access token found."
627 }
628 }
629 w.WriteHeader(200)
630 w.Write([]byte(payload))
631 }))
633 t.opts = NewOptions()
634 t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL)
635 if opts.ProxyUpstream != "" {
636 t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream)
637 }
638 // The CookieSecret must be 32 bytes in order to create the AES
639 // cipher.
640 t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
641 t.opts.ClientID = "slgkj"
642 t.opts.ClientSecret = "gfjgojl"
643 t.opts.CookieSecure = false
644 t.opts.PassAccessToken = opts.PassAccessToken
645 t.opts.Validate()
647 providerURL, _ := url.Parse(t.providerServer.URL)
648 const emailAddress = "michael.bland@gsa.gov"
650 t.opts.provider = NewTestProvider(providerURL, emailAddress)
651 t.proxy = NewOAuthProxy(t.opts, func(email string) bool {
652 return email == emailAddress
653 })
654 return t
657func (patTest *PassAccessTokenTest) Close() {
658 patTest.providerServer.Close()
661func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
662 cookie string) {
663 rw := httptest.NewRecorder()
664 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
665 strings.NewReader(""))
666 if err != nil {
667 return 0, ""
668 }
669 req.AddCookie(patTest.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
670 patTest.proxy.ServeHTTP(rw, req)
671 return rw.Code, rw.HeaderMap["Set-Cookie"][1]
674// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
675// and cookie and returns body and status code.
676func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
677 cookieName := patTest.proxy.CookieName
678 var value string
679 keyPrefix := cookieName + "="
681 for _, field := range strings.Split(cookie, "; ") {
682 value = strings.TrimPrefix(field, keyPrefix)
683 if value != field {
684 break
685 } else {
686 value = ""
687 }
688 }
689 if value == "" {
690 return 0, ""
691 }
693 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
694 if err != nil {
695 return 0, ""
696 }
697 req.AddCookie(&http.Cookie{
698 Name: cookieName,
699 Value: value,
700 Path: "/",
701 Expires: time.Now().Add(time.Duration(24)),
702 HttpOnly: true,
703 })
705 rw := httptest.NewRecorder()
706 patTest.proxy.ServeHTTP(rw, req)
707 return rw.Code, rw.Body.String()
710func TestForwardAccessTokenUpstream(t *testing.T) {
711 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
712 PassAccessToken: true,
713 })
714 defer patTest.Close()
716 // A successful validation will redirect and set the auth cookie.
717 code, cookie := patTest.getCallbackEndpoint()
718 if code != 302 {
719 t.Fatalf("expected 302; got %d", code)
720 }
721 assert.NotEqual(t, nil, cookie)
723 // Now we make a regular request; the access_token from the cookie is
724 // forwarded as the "X-Forwarded-Access-Token" header. The token is
725 // read by the test provider server and written in the response body.
726 code, payload := patTest.getEndpointWithCookie(cookie, "/")
727 if code != 200 {
728 t.Fatalf("expected 200; got %d", code)
729 }
730 assert.Equal(t, "my_auth_token", payload)
733func TestStaticProxyUpstream(t *testing.T) {
734 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
735 PassAccessToken: true,
736 ProxyUpstream: "static://200/static-proxy",
737 })
739 defer patTest.Close()
741 // A successful validation will redirect and set the auth cookie.
742 code, cookie := patTest.getCallbackEndpoint()
743 if code != 302 {
744 t.Fatalf("expected 302; got %d", code)
745 }
746 assert.NotEqual(t, nil, cookie)
748 // Now we make a regular request againts the upstream proxy; And validate
749 // the returned status code through the static proxy.
750 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
751 if code != 200 {
752 t.Fatalf("expected 200; got %d", code)
753 }
754 assert.Equal(t, "Authenticated", payload)
757func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
758 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
759 PassAccessToken: false,
760 })
761 defer patTest.Close()
763 // A successful validation will redirect and set the auth cookie.
764 code, cookie := patTest.getCallbackEndpoint()
765 if code != 302 {
766 t.Fatalf("expected 302; got %d", code)
767 }
768 assert.NotEqual(t, nil, cookie)
770 // Now we make a regular request, but the access token header should
771 // not be present.
772 code, payload := patTest.getEndpointWithCookie(cookie, "/")
773 if code != 200 {
774 t.Fatalf("expected 200; got %d", code)
775 }
776 assert.Equal(t, "No access token found.", payload)
779type SignInPageTest struct {
780 opts *Options
781 proxy *OAuthProxy
782 signInRegexp *regexp.Regexp
783 signInProviderRegexp *regexp.Regexp
786const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
787const signInSkipProvider = `>Found<`
789func NewSignInPageTest(skipProvider bool) *SignInPageTest {
790 var sipTest SignInPageTest
792 sipTest.opts = NewOptions()
793 sipTest.opts.CookieSecret = "adklsj2"
794 sipTest.opts.ClientID = "lkdgj"
795 sipTest.opts.ClientSecret = "sgiufgoi"
796 sipTest.opts.SkipProviderButton = skipProvider
797 sipTest.opts.Validate()
799 sipTest.proxy = NewOAuthProxy(sipTest.opts, func(email string) bool {
800 return true
801 })
802 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
803 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
805 return &sipTest
808func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
809 rw := httptest.NewRecorder()
810 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
811 sipTest.proxy.ServeHTTP(rw, req)
812 return rw.Code, rw.Body.String()
815func TestSignInPageIncludesTargetRedirect(t *testing.T) {
816 sipTest := NewSignInPageTest(false)
817 const endpoint = "/some/random/endpoint"
819 code, body := sipTest.GetEndpoint(endpoint)
820 assert.Equal(t, 403, code)
822 match := sipTest.signInRegexp.FindStringSubmatch(body)
823 if match == nil {
824 t.Fatal("Did not find pattern in body: " +
825 signInRedirectPattern + "\nBody:\n" + body)
826 }
827 if match[1] != endpoint {
828 t.Fatal(`expected redirect to "` + endpoint +
829 `", but was "` + match[1] + `"`)
830 }
833func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
834 sipTest := NewSignInPageTest(false)
835 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
836 assert.Equal(t, 200, code)
838 match := sipTest.signInRegexp.FindStringSubmatch(body)
839 if match == nil {
840 t.Fatal("Did not find pattern in body: " +
841 signInRedirectPattern + "\nBody:\n" + body)
842 }
843 if match[1] != "/" {
844 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
845 }
848func TestSignInPageSkipProvider(t *testing.T) {
849 sipTest := NewSignInPageTest(true)
850 const endpoint = "/some/random/endpoint"
852 code, body := sipTest.GetEndpoint(endpoint)
853 assert.Equal(t, 302, code)
855 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
856 if match == nil {
857 t.Fatal("Did not find pattern in body: " +
858 signInSkipProvider + "\nBody:\n" + body)
859 }
862func TestSignInPageSkipProviderDirect(t *testing.T) {
863 sipTest := NewSignInPageTest(true)
864 const endpoint = "/sign_in"
866 code, body := sipTest.GetEndpoint(endpoint)
867 assert.Equal(t, 302, code)
869 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
870 if match == nil {
871 t.Fatal("Did not find pattern in body: " +
872 signInSkipProvider + "\nBody:\n" + body)
873 }
876type ProcessCookieTest struct {
877 opts *Options
878 proxy *OAuthProxy
879 rw *httptest.ResponseRecorder
880 req *http.Request
881 provider TestProvider
882 responseCode int
883 validateUser bool
886type ProcessCookieTestOpts struct {
887 providerValidateCookieResponse bool
890type OptionsModifier func(*Options)
892func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) *ProcessCookieTest {
893 var pcTest ProcessCookieTest
895 pcTest.opts = NewOptions()
896 for _, modifier := range modifiers {
897 modifier(pcTest.opts)
898 }
899 pcTest.opts.ClientID = "asdfljk"
900 pcTest.opts.ClientSecret = "lkjfdsig"
901 pcTest.opts.CookieSecret = "0123456789abcdefabcd"
902 // First, set the CookieRefresh option so proxy.AesCipher is created,
903 // needed to encrypt the access_token.
904 pcTest.opts.CookieRefresh = time.Hour
905 pcTest.opts.Validate()
907 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
908 return pcTest.validateUser
909 })
910 pcTest.proxy.provider = &TestProvider{
911 ValidToken: opts.providerValidateCookieResponse,
912 }
914 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
915 // access_token validation.
916 pcTest.proxy.CookieRefresh = time.Duration(0)
917 pcTest.rw = httptest.NewRecorder()
918 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
919 pcTest.validateUser = true
920 return &pcTest
923func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
924 return NewProcessCookieTest(ProcessCookieTestOpts{
925 providerValidateCookieResponse: true,
926 })
929func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) *ProcessCookieTest {
930 return NewProcessCookieTest(ProcessCookieTestOpts{
931 providerValidateCookieResponse: true,
932 }, modifiers...)
935func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
936 err := p.proxy.SaveSession(p.rw, p.req, s)
937 if err != nil {
938 return err
939 }
940 for _, cookie := range p.rw.Result().Cookies() {
941 p.req.AddCookie(cookie)
942 }
943 return nil
946func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
947 return p.proxy.LoadCookiedSession(p.req)
950func TestLoadCookiedSession(t *testing.T) {
951 pcTest := NewProcessCookieTestWithDefaults()
953 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: time.Now()}
954 pcTest.SaveSession(startSession)
956 session, err := pcTest.LoadCookiedSession()
957 assert.Equal(t, nil, err)
958 assert.Equal(t, startSession.Email, session.Email)
959 assert.Equal(t, "john.doe@example.com", session.User)
960 assert.Equal(t, startSession.AccessToken, session.AccessToken)
963func TestProcessCookieNoCookieError(t *testing.T) {
964 pcTest := NewProcessCookieTestWithDefaults()
966 session, err := pcTest.LoadCookiedSession()
967 assert.Equal(t, "Cookie \"_oauth2_proxy\" not present", err.Error())
968 if session != nil {
969 t.Errorf("expected nil session. got %#v", session)
970 }
973func TestProcessCookieRefreshNotSet(t *testing.T) {
974 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
975 opts.CookieExpire = time.Duration(23) * time.Hour
976 })
977 reference := time.Now().Add(time.Duration(-2) * time.Hour)
979 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
980 pcTest.SaveSession(startSession)
982 session, err := pcTest.LoadCookiedSession()
983 assert.Equal(t, nil, err)
984 if session.Age() < time.Duration(-2)*time.Hour {
985 t.Errorf("cookie too young %v", session.Age())
986 }
987 assert.Equal(t, startSession.Email, session.Email)
990func TestProcessCookieFailIfCookieExpired(t *testing.T) {
991 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
992 opts.CookieExpire = time.Duration(24) * time.Hour
993 })
994 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
995 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
996 pcTest.SaveSession(startSession)
998 session, err := pcTest.LoadCookiedSession()
999 assert.NotEqual(t, nil, err)
1000 if session != nil {
1001 t.Errorf("expected nil session %#v", session)
1005func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1006 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
1007 opts.CookieExpire = time.Duration(24) * time.Hour
1008 })
1009 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1010 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
1011 pcTest.SaveSession(startSession)
1013 pcTest.proxy.CookieRefresh = time.Hour
1014 session, err := pcTest.LoadCookiedSession()
1015 assert.NotEqual(t, nil, err)
1016 if session != nil {
1017 t.Errorf("expected nil session %#v", session)
1021func NewUserInfoEndpointTest() *ProcessCookieTest {
1022 pcTest := NewProcessCookieTestWithDefaults()
1023 pcTest.req, _ = http.NewRequest("GET",
1024 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1025 return pcTest
1028func TestUserInfoEndpointAccepted(t *testing.T) {
1029 test := NewUserInfoEndpointTest()
1030 startSession := &sessions.SessionState{
1031 Email: "john.doe@example.com", AccessToken: "my_access_token"}
1032 test.SaveSession(startSession)
1034 test.proxy.ServeHTTP(test.rw, test.req)
1035 assert.Equal(t, http.StatusOK, test.rw.Code)
1036 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1037 assert.Equal(t, "{\"email\":\"john.doe@example.com\"}\n", string(bodyBytes))
1040func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1041 test := NewUserInfoEndpointTest()
1043 test.proxy.ServeHTTP(test.rw, test.req)
1044 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1047func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) *ProcessCookieTest {
1048 pcTest := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1049 pcTest.req, _ = http.NewRequest("GET",
1050 pcTest.opts.ProxyPrefix+"/auth", nil)
1051 return pcTest
1054func TestAuthOnlyEndpointAccepted(t *testing.T) {
1055 test := NewAuthOnlyEndpointTest()
1056 startSession := &sessions.SessionState{
1057 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
1058 test.SaveSession(startSession)
1060 test.proxy.ServeHTTP(test.rw, test.req)
1061 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1062 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1063 assert.Equal(t, "", string(bodyBytes))
1066func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1067 test := NewAuthOnlyEndpointTest()
1069 test.proxy.ServeHTTP(test.rw, test.req)
1070 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1071 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1072 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1075func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1076 test := NewAuthOnlyEndpointTest(func(opts *Options) {
1077 opts.CookieExpire = time.Duration(24) * time.Hour
1078 })
1079 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1080 startSession := &sessions.SessionState{
1081 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
1082 test.SaveSession(startSession)
1084 test.proxy.ServeHTTP(test.rw, test.req)
1085 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1086 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1087 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1090func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1091 test := NewAuthOnlyEndpointTest()
1092 startSession := &sessions.SessionState{
1093 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
1094 test.SaveSession(startSession)
1095 test.validateUser = false
1097 test.proxy.ServeHTTP(test.rw, test.req)
1098 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1099 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1100 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1103func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1104 var pcTest ProcessCookieTest
1106 pcTest.opts = NewOptions()
1107 pcTest.opts.SetXAuthRequest = true
1108 pcTest.opts.Validate()
1110 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1111 return pcTest.validateUser
1112 })
1113 pcTest.proxy.provider = &TestProvider{
1114 ValidToken: true,
1117 pcTest.validateUser = true
1119 pcTest.rw = httptest.NewRecorder()
1120 pcTest.req, _ = http.NewRequest("GET",
1121 pcTest.opts.ProxyPrefix+"/auth", nil)
1123 startSession := &sessions.SessionState{
1124 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: time.Now()}
1125 pcTest.SaveSession(startSession)
1127 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1128 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1129 assert.Equal(t, "oauth_user", pcTest.rw.HeaderMap["X-Auth-Request-User"][0])
1130 assert.Equal(t, "oauth_user@example.com", pcTest.rw.HeaderMap["X-Auth-Request-Email"][0])
1133func TestAuthSkippedForPreflightRequests(t *testing.T) {
1134 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1135 w.WriteHeader(200)
1136 w.Write([]byte("response"))
1137 }))
1138 defer upstream.Close()
1140 opts := NewOptions()
1141 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1142 opts.ClientID = "aljsal"
1143 opts.ClientSecret = "jglkfsdgj"
1144 opts.CookieSecret = "dkfjgdls"
1145 opts.SkipAuthPreflight = true
1146 opts.Validate()
1148 upstreamURL, _ := url.Parse(upstream.URL)
1149 opts.provider = NewTestProvider(upstreamURL, "")
1151 proxy := NewOAuthProxy(opts, func(string) bool { return false })
1152 rw := httptest.NewRecorder()
1153 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1154 proxy.ServeHTTP(rw, req)
1156 assert.Equal(t, 200, rw.Code)
1157 assert.Equal(t, "response", rw.Body.String())
1160type SignatureAuthenticator struct {
1161 auth hmacauth.HmacAuth
1164func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1165 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1166 if result == hmacauth.ResultNoSignature {
1167 w.Write([]byte("no signature received"))
1168 } else if result == hmacauth.ResultMatch {
1169 w.Write([]byte("signatures match"))
1170 } else if result == hmacauth.ResultMismatch {
1171 w.Write([]byte("signatures do not match:" +
1172 "\n received: " + headerSig +
1173 "\n computed: " + computedSig))
1174 } else {
1175 panic("Unknown result value: " + result.String())
1179type SignatureTest struct {
1180 opts *Options
1181 upstream *httptest.Server
1182 upstreamHost string
1183 provider *httptest.Server
1184 header http.Header
1185 rw *httptest.ResponseRecorder
1186 authenticator *SignatureAuthenticator
1189func NewSignatureTest() *SignatureTest {
1190 opts := NewOptions()
1191 opts.CookieSecret = "cookie secret"
1192 opts.ClientID = "client ID"
1193 opts.ClientSecret = "client secret"
1194 opts.EmailDomains = []string{"acm.org"}
1196 authenticator := &SignatureAuthenticator{}
1197 upstream := httptest.NewServer(
1198 http.HandlerFunc(authenticator.Authenticate))
1199 upstreamURL, _ := url.Parse(upstream.URL)
1200 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1202 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1203 w.Write([]byte(`{"access_token": "my_auth_token"}`))
1205 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1206 providerURL, _ := url.Parse(provider.URL)
1207 opts.provider = NewTestProvider(providerURL, "mbland@acm.org")
1209 return &SignatureTest{
1210 opts,
1211 upstream,
1212 upstreamURL.Host,
1213 provider,
1214 make(http.Header),
1215 httptest.NewRecorder(),
1216 authenticator,
1220func (st *SignatureTest) Close() {
1221 st.provider.Close()
1222 st.upstream.Close()
1225// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1226// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1227// https://github.com/18F/hmacauth/pull/4
1228type fakeNetConn struct {
1229 reqBody string
1232func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1233 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1234 copy(p, fnc.reqBody)
1235 fnc.reqBody = ""
1236 return bodyLen, io.EOF
1238 return 0, io.EOF
1241func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
1242 err := st.opts.Validate()
1243 if err != nil {
1244 panic(err)
1246 proxy := NewOAuthProxy(st.opts, func(email string) bool { return true })
1248 var bodyBuf io.ReadCloser
1249 if body != "" {
1250 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1252 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1253 req.Header = st.header
1255 state := &sessions.SessionState{
1256 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1257 err = proxy.SaveSession(st.rw, req, state)
1258 if err != nil {
1259 panic(err)
1261 for _, c := range st.rw.Result().Cookies() {
1262 req.AddCookie(c)
1264 // This is used by the upstream to validate the signature.
1265 st.authenticator.auth = hmacauth.NewHmacAuth(
1266 crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
1267 proxy.ServeHTTP(st.rw, req)
1270func TestNoRequestSignature(t *testing.T) {
1271 st := NewSignatureTest()
1272 defer st.Close()
1273 st.MakeRequestWithExpectedKey("GET", "", "")
1274 assert.Equal(t, 200, st.rw.Code)
1275 assert.Equal(t, st.rw.Body.String(), "no signature received")
1278func TestRequestSignatureGetRequest(t *testing.T) {
1279 st := NewSignatureTest()
1280 defer st.Close()
1281 st.opts.SignatureKey = "sha1:7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d"
1282 st.MakeRequestWithExpectedKey("GET", "", "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d")
1283 assert.Equal(t, 200, st.rw.Code)
1284 assert.Equal(t, st.rw.Body.String(), "signatures match")
1287func TestRequestSignaturePostRequest(t *testing.T) {
1288 st := NewSignatureTest()
1289 defer st.Close()
1290 st.opts.SignatureKey = "sha1:d90df39e2d19282840252612dd7c81421a372f61"
1291 payload := `{ "hello": "world!" }`
1292 st.MakeRequestWithExpectedKey("POST", payload, "d90df39e2d19282840252612dd7c81421a372f61")
1293 assert.Equal(t, 200, st.rw.Code)
1294 assert.Equal(t, st.rw.Body.String(), "signatures match")
1297func TestGetRedirect(t *testing.T) {
1298 options := NewOptions()
1299 _ = options.Validate()
1300 require.NotEmpty(t, options.ProxyPrefix)
1301 proxy := NewOAuthProxy(options, func(s string) bool { return false })
1303 tests := []struct {
1304 name string
1305 url string
1306 expectedRedirect string
1307 }{
1309 name: "request outside of ProxyPrefix redirects to original URL",
1310 url: "/foo/bar",
1311 expectedRedirect: "/foo/bar",
1312 },
1314 name: "request under ProxyPrefix redirects to root",
1315 url: proxy.ProxyPrefix + "/foo/bar",
1316 expectedRedirect: "/",
1317 },
1319 for _, tt := range tests {
1320 t.Run(tt.name, func(t *testing.T) {
1321 req, _ := http.NewRequest("GET", tt.url, nil)
1322 redirect, err := proxy.GetRedirect(req)
1324 assert.NoError(t, err)
1325 assert.Equal(t, tt.expectedRedirect, redirect)
1326 })
1330type ajaxRequestTest struct {
1331 opts *Options
1332 proxy *OAuthProxy
1335func newAjaxRequestTest() *ajaxRequestTest {
1336 test := &ajaxRequestTest{}
1337 test.opts = NewOptions()
1338 test.opts.CookieSecret = "sdflsw"
1339 test.opts.ClientID = "gkljfdl"
1340 test.opts.ClientSecret = "sdflkjs"
1341 test.opts.Validate()
1342 test.proxy = NewOAuthProxy(test.opts, func(email string) bool {
1343 return true
1344 })
1345 return test
1348func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1349 rw := httptest.NewRecorder()
1350 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1351 if err != nil {
1352 return 0, nil, err
1354 req.Header = header
1355 test.proxy.ServeHTTP(rw, req)
1356 return rw.Code, rw.Header(), nil
1359func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1360 test := newAjaxRequestTest()
1361 endpoint := "/test"
1363 code, rh, err := test.getEndpoint(endpoint, header)
1364 assert.NoError(t, err)
1365 assert.Equal(t, http.StatusUnauthorized, code)
1366 mime := rh.Get("Content-Type")
1367 assert.Equal(t, applicationJSON, mime)
1369func TestAjaxUnauthorizedRequest1(t *testing.T) {
1370 header := make(http.Header)
1371 header.Add("accept", applicationJSON)
1373 testAjaxUnauthorizedRequest(t, header)
1376func TestAjaxUnauthorizedRequest2(t *testing.T) {
1377 header := make(http.Header)
1378 header.Add("Accept", applicationJSON)
1380 testAjaxUnauthorizedRequest(t, header)
1383func TestAjaxForbiddendRequest(t *testing.T) {
1384 test := newAjaxRequestTest()
1385 endpoint := "/test"
1386 header := make(http.Header)
1387 code, rh, err := test.getEndpoint(endpoint, header)
1388 assert.NoError(t, err)
1389 assert.Equal(t, http.StatusForbidden, code)
1390 mime := rh.Get("Content-Type")
1391 assert.NotEqual(t, applicationJSON, mime)
1394func TestClearSplitCookie(t *testing.T) {
1395 opts := NewOptions()
1396 opts.CookieName = "oauth2"
1397 opts.CookieDomain = "abc"
1398 store, err := cookie.NewCookieSessionStore(&opts.SessionOptions, &opts.CookieOptions)
1399 assert.Equal(t, err, nil)
1400 p := OAuthProxy{CookieName: opts.CookieName, CookieDomain: opts.CookieDomain, sessionStore: store}
1401 var rw = httptest.NewRecorder()
1402 req := httptest.NewRequest("get", "/", nil)
1404 req.AddCookie(&http.Cookie{
1405 Name: "test1",
1406 Value: "test1",
1407 })
1408 req.AddCookie(&http.Cookie{
1409 Name: "oauth2_0",
1410 Value: "oauth2_0",
1411 })
1412 req.AddCookie(&http.Cookie{
1413 Name: "oauth2_1",
1414 Value: "oauth2_1",
1415 })
1417 p.ClearSessionCookie(rw, req)
1418 header := rw.Header()
1420 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1423func TestClearSingleCookie(t *testing.T) {
1424 opts := NewOptions()
1425 opts.CookieName = "oauth2"
1426 opts.CookieDomain = "abc"
1427 store, err := cookie.NewCookieSessionStore(&opts.SessionOptions, &opts.CookieOptions)
1428 assert.Equal(t, err, nil)
1429 p := OAuthProxy{CookieName: opts.CookieName, CookieDomain: opts.CookieDomain, sessionStore: store}
1430 var rw = httptest.NewRecorder()
1431 req := httptest.NewRequest("get", "/", nil)
1433 req.AddCookie(&http.Cookie{
1434 Name: "test1",
1435 Value: "test1",
1436 })
1437 req.AddCookie(&http.Cookie{
1438 Name: "oauth2",
1439 Value: "oauth2",
1440 })
1442 p.ClearSessionCookie(rw, req)
1443 header := rw.Header()
1445 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1448type NoOpKeySet struct {
1451func (NoOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
1452 splitStrings := strings.Split(jwt, ".")
1453 payloadString := splitStrings[1]
1454 jsonString, err := base64.RawURLEncoding.DecodeString(payloadString)
1455 return []byte(jsonString), err
1458func TestGetJwtSession(t *testing.T) {
1459 /* token payload:
1461 "sub": "1234567890",
1462 "aud": "https://test.myapp.com",
1463 "name": "John Doe",
1464 "email": "john@example.com",
1465 "iss": "https://issuer.example.com",
1466 "iat": 1553691215,
1467 "exp": 1912151821
1469 */
1470 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1471 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1472 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1473 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1474 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1475 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1477 keyset := NoOpKeySet{}
1478 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1479 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1481 test := NewAuthOnlyEndpointTest(func(opts *Options) {
1482 opts.PassAuthorization = true
1483 opts.SetAuthorization = true
1484 opts.SetXAuthRequest = true
1485 opts.SkipJwtBearerTokens = true
1486 opts.jwtBearerVerifiers = append(opts.jwtBearerVerifiers, verifier)
1487 })
1488 tp, _ := test.proxy.provider.(*TestProvider)
1489 tp.GroupValidator = func(s string) bool {
1490 return true
1493 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1494 test.req.Header = map[string][]string{
1495 "Authorization": {authHeader},
1498 // Bearer
1499 session, _ := test.proxy.GetJwtSession(test.req)
1500 assert.Equal(t, session.User, "john@example.com")
1501 assert.Equal(t, session.Email, "john@example.com")
1502 assert.Equal(t, session.ExpiresOn, time.Unix(1912151821, 0))
1503 assert.Equal(t, session.IDToken, goodJwt)
1505 test.proxy.ServeHTTP(test.rw, test.req)
1506 if test.rw.Code >= 400 {
1507 t.Fatalf("expected 3xx got %d", test.rw.Code)
1510 // Check PassAuthorization, should overwrite Basic header
1511 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
1512 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "john@example.com")
1513 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
1515 // SetAuthorization and SetXAuthRequest
1516 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
1517 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "john@example.com")
1518 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
1521func TestFindJwtBearerToken(t *testing.T) {
1522 p := OAuthProxy{CookieName: "oauth2", CookieDomain: "abc"}
1523 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}}
1525 validToken := "eyJfoobar.eyJfoobar.12345asdf"
1526 var token string
1528 // Bearer
1529 getReq.Header = map[string][]string{
1530 "Authorization": {fmt.Sprintf("Bearer %s", validToken)},
1533 token, _ = p.findBearerToken(getReq)
1534 assert.Equal(t, validToken, token)
1536 // Basic - no password
1537 getReq.SetBasicAuth(token, "")
1538 token, _ = p.findBearerToken(getReq)
1539 assert.Equal(t, validToken, token)
1541 // Basic - sentinel password
1542 getReq.SetBasicAuth(token, "x-oauth-basic")
1543 token, _ = p.findBearerToken(getReq)
1544 assert.Equal(t, validToken, token)
1546 // Basic - any username, password matching jwt pattern
1547 getReq.SetBasicAuth("any-username-you-could-wish-for", token)
1548 token, _ = p.findBearerToken(getReq)
1549 assert.Equal(t, validToken, token)
1551 failures := []string{
1552 // Too many parts
1553 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1554 // Not enough parts
1555 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA",
1556 // Invalid encrypted key
1557 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.//////.dGVzdA.dGVzdA.dGVzdA",
1558 // Invalid IV
1559 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.//////.dGVzdA.dGVzdA",
1560 // Invalid ciphertext
1561 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.//////.dGVzdA",
1562 // Invalid tag
1563 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.//////",
1564 // Invalid header
1565 "W10.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1566 // Invalid header
1567 "######.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1568 // Missing alc/enc params
1569 "e30.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1572 for _, failure := range failures {
1573 getReq.Header = map[string][]string{
1574 "Authorization": {fmt.Sprintf("Bearer %s", failure)},
1576 _, err := p.findBearerToken(getReq)
1577 assert.Error(t, err)
1580 fmt.Printf("%s", token)