git.iko.soy/typeable/oauth2-proxy
on custom-timeout help sign in
typeable/oauth2-proxy/oauthproxy_test.go
1697 lines 51 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/oauth2-proxy/oauth2-proxy/pkg/apis/options"
22 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
23 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
24 "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie"
25 "github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
26 "github.com/oauth2-proxy/oauth2-proxy/providers"
27 "github.com/stretchr/testify/assert"
28 "github.com/stretchr/testify/require"
29 "golang.org/x/net/websocket"
32const (
33 // The rawCookieSecret is 32 bytes and the base64CookieSecret is the base64
34 // encoded version of this.
35 rawCookieSecret = "secretthirtytwobytes+abcdefghijk"
36 base64CookieSecret = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpamsK"
39func init() {
40 logger.SetFlags(logger.Lshortfile)
44type WebSocketOrRestHandler struct {
45 restHandler http.Handler
46 wsHandler http.Handler
49func (h *WebSocketOrRestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
50 if r.Header.Get("Upgrade") == "websocket" {
51 h.wsHandler.ServeHTTP(w, r)
52 } else {
53 h.restHandler.ServeHTTP(w, r)
54 }
57func TestWebSocketProxy(t *testing.T) {
58 handler := WebSocketOrRestHandler{
59 restHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
60 w.WriteHeader(200)
61 hostname, _, _ := net.SplitHostPort(r.Host)
62 w.Write([]byte(hostname))
63 }),
64 wsHandler: websocket.Handler(func(ws *websocket.Conn) {
65 defer ws.Close()
66 var data []byte
67 err := websocket.Message.Receive(ws, &data)
68 if err != nil {
69 t.Fatalf("err %s", err)
70 return
71 }
72 err = websocket.Message.Send(ws, data)
73 if err != nil {
74 t.Fatalf("err %s", err)
75 }
76 }),
77 }
78 backend := httptest.NewServer(&handler)
79 defer backend.Close()
81 backendURL, _ := url.Parse(backend.URL)
83 options := options.NewOptions()
84 var auth hmacauth.HmacAuth
85 options.PassHostHeader = true
86 proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, options, auth)
87 frontend := httptest.NewServer(proxyHandler)
88 defer frontend.Close()
90 frontendURL, _ := url.Parse(frontend.URL)
91 frontendWSURL := "ws://" + frontendURL.Host + "/"
93 ws, err := websocket.Dial(frontendWSURL, "", "http://localhost/")
94 if err != nil {
95 t.Fatalf("err %s", err)
96 }
97 request := []byte("hello, world!")
98 err = websocket.Message.Send(ws, request)
99 if err != nil {
100 t.Fatalf("err %s", err)
101 }
102 var response = make([]byte, 1024)
103 websocket.Message.Receive(ws, &response)
104 if err != nil {
105 t.Fatalf("err %s", err)
106 }
107 if g, e := string(request), string(response); g != e {
108 t.Errorf("got body %q; expected %q", g, e)
109 }
111 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
112 res, _ := http.DefaultClient.Do(getReq)
113 bodyBytes, _ := ioutil.ReadAll(res.Body)
114 backendHostname, _, _ := net.SplitHostPort(backendURL.Host)
115 if g, e := string(bodyBytes), backendHostname; g != e {
116 t.Errorf("got body %q; expected %q", g, e)
117 }
120func TestNewReverseProxy(t *testing.T) {
121 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
122 w.WriteHeader(200)
123 hostname, _, _ := net.SplitHostPort(r.Host)
124 w.Write([]byte(hostname))
125 }))
126 defer backend.Close()
128 backendURL, _ := url.Parse(backend.URL)
129 backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host)
130 backendHost := net.JoinHostPort(backendHostname, backendPort)
131 proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
133 proxyHandler := NewReverseProxy(proxyURL, &options.Options{FlushInterval: time.Second})
134 setProxyUpstreamHostHeader(proxyHandler, proxyURL)
135 frontend := httptest.NewServer(proxyHandler)
136 defer frontend.Close()
138 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
139 res, _ := http.DefaultClient.Do(getReq)
140 bodyBytes, _ := ioutil.ReadAll(res.Body)
141 if g, e := string(bodyBytes), backendHostname; g != e {
142 t.Errorf("got body %q; expected %q", g, e)
143 }
146func TestEncodedSlashes(t *testing.T) {
147 var seen string
148 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
149 w.WriteHeader(200)
150 seen = r.RequestURI
151 }))
152 defer backend.Close()
154 b, _ := url.Parse(backend.URL)
155 proxyHandler := NewReverseProxy(b, &options.Options{FlushInterval: time.Second})
156 setProxyDirector(proxyHandler)
157 frontend := httptest.NewServer(proxyHandler)
158 defer frontend.Close()
160 f, _ := url.Parse(frontend.URL)
161 encodedPath := "/a%2Fb/?c=1"
162 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: f.Host, Opaque: encodedPath}}
163 _, err := http.DefaultClient.Do(getReq)
164 if err != nil {
165 t.Fatalf("err %s", err)
166 }
167 if seen != encodedPath {
168 t.Errorf("got bad request %q expected %q", seen, encodedPath)
169 }
172func TestRobotsTxt(t *testing.T) {
173 opts := options.NewOptions()
174 opts.ClientID = "asdlkjx"
175 opts.ClientSecret = "alkgks"
176 opts.Cookie.Secret = rawCookieSecret
177 validation.Validate(opts)
179 proxy := NewOAuthProxy(opts, func(string) bool { return true })
180 rw := httptest.NewRecorder()
181 req, _ := http.NewRequest("GET", "/robots.txt", nil)
182 proxy.ServeHTTP(rw, req)
183 assert.Equal(t, 200, rw.Code)
184 assert.Equal(t, "User-agent: *\nDisallow: /", rw.Body.String())
187func TestIsValidRedirect(t *testing.T) {
188 opts := options.NewOptions()
189 opts.ClientID = "skdlfj"
190 opts.ClientSecret = "fgkdsgj"
191 opts.Cookie.Secret = base64CookieSecret
192 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
193 opts.WhitelistDomains = []string{
194 "foo.bar",
195 ".bar.foo",
196 "port.bar:8080",
197 ".sub.port.bar:8080",
198 "anyport.bar:*",
199 ".sub.anyport.bar:*",
200 }
201 validation.Validate(opts)
203 proxy := NewOAuthProxy(opts, func(string) bool { return true })
205 testCases := []struct {
206 Desc, Redirect string
207 ExpectedResult bool
208 }{
209 {
210 Desc: "noRD",
211 Redirect: "",
212 ExpectedResult: false,
213 },
214 {
215 Desc: "singleSlash",
216 Redirect: "/redirect",
217 ExpectedResult: true,
218 },
219 {
220 Desc: "doubleSlash",
221 Redirect: "//redirect",
222 ExpectedResult: false,
223 },
224 {
225 Desc: "validHTTP",
226 Redirect: "http://foo.bar/redirect",
227 ExpectedResult: true,
228 },
229 {
230 Desc: "validHTTPS",
231 Redirect: "https://foo.bar/redirect",
232 ExpectedResult: true,
233 },
234 {
235 Desc: "invalidHTTPSubdomain",
236 Redirect: "http://baz.foo.bar/redirect",
237 ExpectedResult: false,
238 },
239 {
240 Desc: "invalidHTTPSSubdomain",
241 Redirect: "https://baz.foo.bar/redirect",
242 ExpectedResult: false,
243 },
244 {
245 Desc: "validHTTPSubdomain",
246 Redirect: "http://baz.bar.foo/redirect",
247 ExpectedResult: true,
248 },
249 {
250 Desc: "validHTTPSSubdomain",
251 Redirect: "https://baz.bar.foo/redirect",
252 ExpectedResult: true,
253 },
254 {
255 Desc: "validHTTPDomain",
256 Redirect: "http://bar.foo/redirect",
257 ExpectedResult: true,
258 },
259 {
260 Desc: "invalidHTTP1",
261 Redirect: "http://foo.bar.evil.corp/redirect",
262 ExpectedResult: false,
263 },
264 {
265 Desc: "invalidHTTPS1",
266 Redirect: "https://foo.bar.evil.corp/redirect",
267 ExpectedResult: false,
268 },
269 {
270 Desc: "invalidHTTP2",
271 Redirect: "http://evil.corp/redirect?rd=foo.bar",
272 ExpectedResult: false,
273 },
274 {
275 Desc: "invalidHTTPS2",
276 Redirect: "https://evil.corp/redirect?rd=foo.bar",
277 ExpectedResult: false,
278 },
279 {
280 Desc: "invalidPort",
281 Redirect: "https://evil.corp:3838/redirect",
282 ExpectedResult: false,
283 },
284 {
285 Desc: "invalidEmptyPort",
286 Redirect: "http://foo.bar:3838/redirect",
287 ExpectedResult: false,
288 },
289 {
290 Desc: "invalidEmptyPortSubdomain",
291 Redirect: "http://baz.bar.foo:3838/redirect",
292 ExpectedResult: false,
293 },
294 {
295 Desc: "validSpecificPort",
296 Redirect: "http://port.bar:8080/redirect",
297 ExpectedResult: true,
298 },
299 {
300 Desc: "invalidSpecificPort",
301 Redirect: "http://port.bar:3838/redirect",
302 ExpectedResult: false,
303 },
304 {
305 Desc: "validSpecificPortSubdomain",
306 Redirect: "http://foo.sub.port.bar:8080/redirect",
307 ExpectedResult: true,
308 },
309 {
310 Desc: "invalidSpecificPortSubdomain",
311 Redirect: "http://foo.sub.port.bar:3838/redirect",
312 ExpectedResult: false,
313 },
314 {
315 Desc: "validAnyPort1",
316 Redirect: "http://anyport.bar:8080/redirect",
317 ExpectedResult: true,
318 },
319 {
320 Desc: "validAnyPort2",
321 Redirect: "http://anyport.bar:8081/redirect",
322 ExpectedResult: true,
323 },
324 {
325 Desc: "validAnyPortSubdomain1",
326 Redirect: "http://a.sub.anyport.bar:8080/redirect",
327 ExpectedResult: true,
328 },
329 {
330 Desc: "validAnyPortSubdomain2",
331 Redirect: "http://a.sub.anyport.bar:8081/redirect",
332 ExpectedResult: true,
333 },
334 {
335 Desc: "openRedirect1",
336 Redirect: "/\\evil.com",
337 ExpectedResult: false,
338 },
339 {
340 Desc: "openRedirectSpace1",
341 Redirect: "/ /evil.com",
342 ExpectedResult: false,
343 },
344 {
345 Desc: "openRedirectSpace2",
346 Redirect: "/ \\evil.com",
347 ExpectedResult: false,
348 },
349 {
350 Desc: "openRedirectTab1",
351 Redirect: "/\t/evil.com",
352 ExpectedResult: false,
353 },
354 {
355 Desc: "openRedirectTab2",
356 Redirect: "/\t\\evil.com",
357 ExpectedResult: false,
358 },
359 {
360 Desc: "openRedirectVerticalTab1",
361 Redirect: "/\v/evil.com",
362 ExpectedResult: false,
363 },
364 {
365 Desc: "openRedirectVerticalTab2",
366 Redirect: "/\v\\evil.com",
367 ExpectedResult: false,
368 },
369 {
370 Desc: "openRedirectNewLine1",
371 Redirect: "/\n/evil.com",
372 ExpectedResult: false,
373 },
374 {
375 Desc: "openRedirectNewLine2",
376 Redirect: "/\n\\evil.com",
377 ExpectedResult: false,
378 },
379 {
380 Desc: "openRedirectCarriageReturn1",
381 Redirect: "/\r/evil.com",
382 ExpectedResult: false,
383 },
384 {
385 Desc: "openRedirectCarriageReturn2",
386 Redirect: "/\r\\evil.com",
387 ExpectedResult: false,
388 },
389 }
391 for _, tc := range testCases {
392 t.Run(tc.Desc, func(t *testing.T) {
393 result := proxy.IsValidRedirect(tc.Redirect)
395 if result != tc.ExpectedResult {
396 t.Errorf("expected %t got %t", tc.ExpectedResult, result)
397 }
398 })
399 }
402type TestProvider struct {
403 *providers.ProviderData
404 EmailAddress string
405 ValidToken bool
406 GroupValidator func(string) bool
409var _ providers.Provider = (*TestProvider)(nil)
411func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
412 return &TestProvider{
413 ProviderData: &providers.ProviderData{
414 ProviderName: "Test Provider",
415 LoginURL: &url.URL{
416 Scheme: "http",
417 Host: providerURL.Host,
418 Path: "/oauth/authorize",
419 },
420 RedeemURL: &url.URL{
421 Scheme: "http",
422 Host: providerURL.Host,
423 Path: "/oauth/token",
424 },
425 ProfileURL: &url.URL{
426 Scheme: "http",
427 Host: providerURL.Host,
428 Path: "/api/v1/profile",
429 },
430 Scope: "profile.email",
431 },
432 EmailAddress: emailAddress,
433 GroupValidator: func(s string) bool {
434 return true
435 },
436 }
439func (tp *TestProvider) GetEmailAddress(ctx context.Context, session *sessions.SessionState) (string, error) {
440 return tp.EmailAddress, nil
443func (tp *TestProvider) ValidateSessionState(ctx context.Context, session *sessions.SessionState) bool {
444 return tp.ValidToken
447func TestBasicAuthPassword(t *testing.T) {
448 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
449 logger.Printf("%#v", r)
450 var payload string
451 switch r.URL.Path {
452 case "/oauth/token":
453 payload = `{"access_token": "my_auth_token"}`
454 default:
455 payload = r.Header.Get("Authorization")
456 if payload == "" {
457 payload = "No Authorization header found."
458 }
459 }
460 w.WriteHeader(200)
461 w.Write([]byte(payload))
462 }))
463 opts := options.NewOptions()
464 opts.Upstreams = append(opts.Upstreams, providerServer.URL)
465 // The CookieSecret must be 32 bytes in order to create the AES
466 // cipher.
467 opts.Cookie.Secret = "xyzzyplughxyzzyplughxyzzyplughxp"
468 opts.ClientID = "dlgkj"
469 opts.ClientSecret = "alkgret"
470 opts.Cookie.Secure = false
471 opts.PassBasicAuth = true
472 opts.SetBasicAuth = true
473 opts.PassUserHeaders = true
474 opts.PreferEmailToUser = true
475 opts.BasicAuthPassword = "This is a secure password"
476 validation.Validate(opts)
478 providerURL, _ := url.Parse(providerServer.URL)
479 const emailAddress = "john.doe@example.com"
481 opts.SetProvider(NewTestProvider(providerURL, emailAddress))
482 proxy := NewOAuthProxy(opts, func(email string) bool {
483 return email == emailAddress
484 })
486 rw := httptest.NewRecorder()
487 req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
488 strings.NewReader(""))
489 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
490 proxy.ServeHTTP(rw, req)
491 if rw.Code >= 400 {
492 t.Fatalf("expected 3xx got %d", rw.Code)
493 }
494 cookie := rw.Header().Values("Set-Cookie")[1]
496 cookieName := proxy.CookieName
497 var value string
498 keyPrefix := cookieName + "="
500 for _, field := range strings.Split(cookie, "; ") {
501 value = strings.TrimPrefix(field, keyPrefix)
502 if value != field {
503 break
504 } else {
505 value = ""
506 }
507 }
509 req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
510 req.AddCookie(&http.Cookie{
511 Name: cookieName,
512 Value: value,
513 Path: "/",
514 Expires: time.Now().Add(time.Duration(24)),
515 HttpOnly: true,
516 })
517 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
519 rw = httptest.NewRecorder()
520 proxy.ServeHTTP(rw, req)
522 // The username in the basic auth credentials is expected to be equal to the email address from the
523 // auth response, so we use the same variable here.
524 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
525 assert.Equal(t, expectedHeader, rw.Body.String())
526 providerServer.Close()
529func TestBasicAuthWithEmail(t *testing.T) {
530 opts := options.NewOptions()
531 opts.PassBasicAuth = true
532 opts.PassUserHeaders = false
533 opts.PreferEmailToUser = false
534 opts.BasicAuthPassword = "This is a secure password"
535 validation.Validate(opts)
537 const emailAddress = "john.doe@example.com"
538 const userName = "9fcab5c9b889a557"
540 // The username in the basic auth credentials is expected to be equal to the email address from the
541 expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
542 expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
544 session := &sessions.SessionState{
545 User: userName,
546 Email: emailAddress,
547 AccessToken: "oauth_token",
548 CreatedAt: time.Now(),
549 }
550 {
551 rw := httptest.NewRecorder()
552 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
553 proxy := NewOAuthProxy(opts, func(email string) bool {
554 return email == emailAddress
555 })
556 proxy.addHeadersForProxying(rw, req, session)
557 assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
558 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
559 }
561 opts.PreferEmailToUser = true
562 {
563 rw := httptest.NewRecorder()
564 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
566 proxy := NewOAuthProxy(opts, func(email string) bool {
567 return email == emailAddress
568 })
569 proxy.addHeadersForProxying(rw, req, session)
570 assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
571 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
572 }
575func TestPassUserHeadersWithEmail(t *testing.T) {
576 opts := options.NewOptions()
577 opts.PassBasicAuth = false
578 opts.PassUserHeaders = true
579 opts.PreferEmailToUser = false
580 validation.Validate(opts)
582 const emailAddress = "john.doe@example.com"
583 const userName = "9fcab5c9b889a557"
585 session := &sessions.SessionState{
586 User: userName,
587 Email: emailAddress,
588 AccessToken: "oauth_token",
589 CreatedAt: time.Now(),
590 }
591 {
592 rw := httptest.NewRecorder()
593 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
594 proxy := NewOAuthProxy(opts, func(email string) bool {
595 return email == emailAddress
596 })
597 proxy.addHeadersForProxying(rw, req, session)
598 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
599 }
601 opts.PreferEmailToUser = true
602 {
603 rw := httptest.NewRecorder()
604 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
606 proxy := NewOAuthProxy(opts, func(email string) bool {
607 return email == emailAddress
608 })
609 proxy.addHeadersForProxying(rw, req, session)
610 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
611 }
614type PassAccessTokenTest struct {
615 providerServer *httptest.Server
616 proxy *OAuthProxy
617 opts *options.Options
620type PassAccessTokenTestOptions struct {
621 PassAccessToken bool
622 ProxyUpstream string
625func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
626 t := &PassAccessTokenTest{}
628 t.providerServer = httptest.NewServer(
629 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
630 var payload string
631 switch r.URL.Path {
632 case "/oauth/token":
633 payload = `{"access_token": "my_auth_token"}`
634 default:
635 payload = r.Header.Get("X-Forwarded-Access-Token")
636 if payload == "" {
637 payload = "No access token found."
638 }
639 }
640 w.WriteHeader(200)
641 w.Write([]byte(payload))
642 }))
644 t.opts = options.NewOptions()
645 t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL)
646 if opts.ProxyUpstream != "" {
647 t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream)
648 }
649 // The CookieSecret must be 32 bytes in order to create the AES
650 // cipher.
651 t.opts.Cookie.Secret = "xyzzyplughxyzzyplughxyzzyplughxp"
652 t.opts.ClientID = "slgkj"
653 t.opts.ClientSecret = "gfjgojl"
654 t.opts.Cookie.Secure = false
655 t.opts.PassAccessToken = opts.PassAccessToken
656 validation.Validate(t.opts)
658 providerURL, _ := url.Parse(t.providerServer.URL)
659 const emailAddress = "michael.bland@gsa.gov"
661 t.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
662 t.proxy = NewOAuthProxy(t.opts, func(email string) bool {
663 return email == emailAddress
664 })
665 return t
668func (patTest *PassAccessTokenTest) Close() {
669 patTest.providerServer.Close()
672func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
673 cookie string) {
674 rw := httptest.NewRecorder()
675 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
676 strings.NewReader(""))
677 if err != nil {
678 return 0, ""
679 }
680 req.AddCookie(patTest.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
681 patTest.proxy.ServeHTTP(rw, req)
682 return rw.Code, rw.Header().Values("Set-Cookie")[1]
685// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
686// and cookie and returns body and status code.
687func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
688 cookieName := patTest.proxy.CookieName
689 var value string
690 keyPrefix := cookieName + "="
692 for _, field := range strings.Split(cookie, "; ") {
693 value = strings.TrimPrefix(field, keyPrefix)
694 if value != field {
695 break
696 } else {
697 value = ""
698 }
699 }
700 if value == "" {
701 return 0, ""
702 }
704 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
705 if err != nil {
706 return 0, ""
707 }
708 req.AddCookie(&http.Cookie{
709 Name: cookieName,
710 Value: value,
711 Path: "/",
712 Expires: time.Now().Add(time.Duration(24)),
713 HttpOnly: true,
714 })
716 rw := httptest.NewRecorder()
717 patTest.proxy.ServeHTTP(rw, req)
718 return rw.Code, rw.Body.String()
721func TestForwardAccessTokenUpstream(t *testing.T) {
722 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
723 PassAccessToken: true,
724 })
725 defer patTest.Close()
727 // A successful validation will redirect and set the auth cookie.
728 code, cookie := patTest.getCallbackEndpoint()
729 if code != 302 {
730 t.Fatalf("expected 302; got %d", code)
731 }
732 assert.NotEqual(t, nil, cookie)
734 // Now we make a regular request; the access_token from the cookie is
735 // forwarded as the "X-Forwarded-Access-Token" header. The token is
736 // read by the test provider server and written in the response body.
737 code, payload := patTest.getEndpointWithCookie(cookie, "/")
738 if code != 200 {
739 t.Fatalf("expected 200; got %d", code)
740 }
741 assert.Equal(t, "my_auth_token", payload)
744func TestStaticProxyUpstream(t *testing.T) {
745 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
746 PassAccessToken: true,
747 ProxyUpstream: "static://200/static-proxy",
748 })
750 defer patTest.Close()
752 // A successful validation will redirect and set the auth cookie.
753 code, cookie := patTest.getCallbackEndpoint()
754 if code != 302 {
755 t.Fatalf("expected 302; got %d", code)
756 }
757 assert.NotEqual(t, nil, cookie)
759 // Now we make a regular request against the upstream proxy; And validate
760 // the returned status code through the static proxy.
761 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
762 if code != 200 {
763 t.Fatalf("expected 200; got %d", code)
764 }
765 assert.Equal(t, "Authenticated", payload)
768func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
769 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
770 PassAccessToken: false,
771 })
772 defer patTest.Close()
774 // A successful validation will redirect and set the auth cookie.
775 code, cookie := patTest.getCallbackEndpoint()
776 if code != 302 {
777 t.Fatalf("expected 302; got %d", code)
778 }
779 assert.NotEqual(t, nil, cookie)
781 // Now we make a regular request, but the access token header should
782 // not be present.
783 code, payload := patTest.getEndpointWithCookie(cookie, "/")
784 if code != 200 {
785 t.Fatalf("expected 200; got %d", code)
786 }
787 assert.Equal(t, "No access token found.", payload)
790type SignInPageTest struct {
791 opts *options.Options
792 proxy *OAuthProxy
793 signInRegexp *regexp.Regexp
794 signInProviderRegexp *regexp.Regexp
797const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
798const signInSkipProvider = `>Found<`
800func NewSignInPageTest(skipProvider bool) *SignInPageTest {
801 var sipTest SignInPageTest
803 sipTest.opts = options.NewOptions()
804 sipTest.opts.Cookie.Secret = rawCookieSecret
805 sipTest.opts.ClientID = "lkdgj"
806 sipTest.opts.ClientSecret = "sgiufgoi"
807 sipTest.opts.SkipProviderButton = skipProvider
808 validation.Validate(sipTest.opts)
810 sipTest.proxy = NewOAuthProxy(sipTest.opts, func(email string) bool {
811 return true
812 })
813 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
814 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
816 return &sipTest
819func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
820 rw := httptest.NewRecorder()
821 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
822 sipTest.proxy.ServeHTTP(rw, req)
823 return rw.Code, rw.Body.String()
826func TestSignInPageIncludesTargetRedirect(t *testing.T) {
827 sipTest := NewSignInPageTest(false)
828 const endpoint = "/some/random/endpoint"
830 code, body := sipTest.GetEndpoint(endpoint)
831 assert.Equal(t, 403, code)
833 match := sipTest.signInRegexp.FindStringSubmatch(body)
834 if match == nil {
835 t.Fatal("Did not find pattern in body: " +
836 signInRedirectPattern + "\nBody:\n" + body)
837 }
838 if match[1] != endpoint {
839 t.Fatal(`expected redirect to "` + endpoint +
840 `", but was "` + match[1] + `"`)
841 }
844func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
845 sipTest := NewSignInPageTest(false)
846 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
847 assert.Equal(t, 200, code)
849 match := sipTest.signInRegexp.FindStringSubmatch(body)
850 if match == nil {
851 t.Fatal("Did not find pattern in body: " +
852 signInRedirectPattern + "\nBody:\n" + body)
853 }
854 if match[1] != "/" {
855 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
856 }
859func TestSignInPageSkipProvider(t *testing.T) {
860 sipTest := NewSignInPageTest(true)
861 const endpoint = "/some/random/endpoint"
863 code, body := sipTest.GetEndpoint(endpoint)
864 assert.Equal(t, 302, code)
866 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
867 if match == nil {
868 t.Fatal("Did not find pattern in body: " +
869 signInSkipProvider + "\nBody:\n" + body)
870 }
873func TestSignInPageSkipProviderDirect(t *testing.T) {
874 sipTest := NewSignInPageTest(true)
875 const endpoint = "/sign_in"
877 code, body := sipTest.GetEndpoint(endpoint)
878 assert.Equal(t, 302, code)
880 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
881 if match == nil {
882 t.Fatal("Did not find pattern in body: " +
883 signInSkipProvider + "\nBody:\n" + body)
884 }
887type ProcessCookieTest struct {
888 opts *options.Options
889 proxy *OAuthProxy
890 rw *httptest.ResponseRecorder
891 req *http.Request
892 validateUser bool
895type ProcessCookieTestOpts struct {
896 providerValidateCookieResponse bool
899type OptionsModifier func(*options.Options)
901func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) *ProcessCookieTest {
902 var pcTest ProcessCookieTest
904 pcTest.opts = options.NewOptions()
905 for _, modifier := range modifiers {
906 modifier(pcTest.opts)
907 }
908 pcTest.opts.ClientID = "asdfljk"
909 pcTest.opts.ClientSecret = "lkjfdsig"
910 pcTest.opts.Cookie.Secret = "0123456789abcdef0123456789abcdef"
911 // First, set the CookieRefresh option so proxy.AesCipher is created,
912 // needed to encrypt the access_token.
913 pcTest.opts.Cookie.Refresh = time.Hour
914 validation.Validate(pcTest.opts)
916 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
917 return pcTest.validateUser
918 })
919 pcTest.proxy.provider = &TestProvider{
920 ValidToken: opts.providerValidateCookieResponse,
921 }
923 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
924 // access_token validation.
925 pcTest.proxy.CookieRefresh = time.Duration(0)
926 pcTest.rw = httptest.NewRecorder()
927 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
928 pcTest.validateUser = true
929 return &pcTest
932func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
933 return NewProcessCookieTest(ProcessCookieTestOpts{
934 providerValidateCookieResponse: true,
935 })
938func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) *ProcessCookieTest {
939 return NewProcessCookieTest(ProcessCookieTestOpts{
940 providerValidateCookieResponse: true,
941 }, modifiers...)
944func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
945 err := p.proxy.SaveSession(p.rw, p.req, s)
946 if err != nil {
947 return err
948 }
949 for _, cookie := range p.rw.Result().Cookies() {
950 p.req.AddCookie(cookie)
951 }
952 return nil
955func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
956 return p.proxy.LoadCookiedSession(p.req)
959func TestLoadCookiedSession(t *testing.T) {
960 pcTest := NewProcessCookieTestWithDefaults()
962 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: time.Now()}
963 pcTest.SaveSession(startSession)
965 session, err := pcTest.LoadCookiedSession()
966 assert.Equal(t, nil, err)
967 assert.Equal(t, startSession.Email, session.Email)
968 assert.Equal(t, "", session.User)
969 assert.Equal(t, startSession.AccessToken, session.AccessToken)
972func TestProcessCookieNoCookieError(t *testing.T) {
973 pcTest := NewProcessCookieTestWithDefaults()
975 session, err := pcTest.LoadCookiedSession()
976 assert.Equal(t, "cookie \"_oauth2_proxy\" not present", err.Error())
977 if session != nil {
978 t.Errorf("expected nil session. got %#v", session)
979 }
982func TestProcessCookieRefreshNotSet(t *testing.T) {
983 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
984 opts.Cookie.Expire = time.Duration(23) * time.Hour
985 })
986 reference := time.Now().Add(time.Duration(-2) * time.Hour)
988 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
989 pcTest.SaveSession(startSession)
991 session, err := pcTest.LoadCookiedSession()
992 assert.Equal(t, nil, err)
993 if session.Age() < time.Duration(-2)*time.Hour {
994 t.Errorf("cookie too young %v", session.Age())
995 }
996 assert.Equal(t, startSession.Email, session.Email)
999func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1000 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1001 opts.Cookie.Expire = time.Duration(24) * time.Hour
1002 })
1003 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1004 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
1005 pcTest.SaveSession(startSession)
1007 session, err := pcTest.LoadCookiedSession()
1008 assert.NotEqual(t, nil, err)
1009 if session != nil {
1010 t.Errorf("expected nil session %#v", session)
1014func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1015 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1016 opts.Cookie.Expire = time.Duration(24) * time.Hour
1017 })
1018 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1019 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
1020 pcTest.SaveSession(startSession)
1022 pcTest.proxy.CookieRefresh = time.Hour
1023 session, err := pcTest.LoadCookiedSession()
1024 assert.NotEqual(t, nil, err)
1025 if session != nil {
1026 t.Errorf("expected nil session %#v", session)
1030func NewUserInfoEndpointTest() *ProcessCookieTest {
1031 pcTest := NewProcessCookieTestWithDefaults()
1032 pcTest.req, _ = http.NewRequest("GET",
1033 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1034 return pcTest
1037func TestUserInfoEndpointAccepted(t *testing.T) {
1038 test := NewUserInfoEndpointTest()
1039 startSession := &sessions.SessionState{
1040 Email: "john.doe@example.com", AccessToken: "my_access_token"}
1041 test.SaveSession(startSession)
1043 test.proxy.ServeHTTP(test.rw, test.req)
1044 assert.Equal(t, http.StatusOK, test.rw.Code)
1045 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1046 assert.Equal(t, "{\"email\":\"john.doe@example.com\"}\n", string(bodyBytes))
1049func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1050 test := NewUserInfoEndpointTest()
1052 test.proxy.ServeHTTP(test.rw, test.req)
1053 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1056func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) *ProcessCookieTest {
1057 pcTest := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1058 pcTest.req, _ = http.NewRequest("GET",
1059 pcTest.opts.ProxyPrefix+"/auth", nil)
1060 return pcTest
1063func TestAuthOnlyEndpointAccepted(t *testing.T) {
1064 test := NewAuthOnlyEndpointTest()
1065 startSession := &sessions.SessionState{
1066 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
1067 test.SaveSession(startSession)
1069 test.proxy.ServeHTTP(test.rw, test.req)
1070 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1071 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1072 assert.Equal(t, "", string(bodyBytes))
1075func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1076 test := NewAuthOnlyEndpointTest()
1078 test.proxy.ServeHTTP(test.rw, test.req)
1079 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1080 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1081 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1084func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1085 test := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1086 opts.Cookie.Expire = time.Duration(24) * time.Hour
1087 })
1088 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1089 startSession := &sessions.SessionState{
1090 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
1091 test.SaveSession(startSession)
1093 test.proxy.ServeHTTP(test.rw, test.req)
1094 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1095 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1096 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1099func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1100 test := NewAuthOnlyEndpointTest()
1101 startSession := &sessions.SessionState{
1102 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
1103 test.SaveSession(startSession)
1104 test.validateUser = false
1106 test.proxy.ServeHTTP(test.rw, test.req)
1107 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1108 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1109 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1112func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1113 var pcTest ProcessCookieTest
1115 pcTest.opts = options.NewOptions()
1116 pcTest.opts.SetXAuthRequest = true
1117 validation.Validate(pcTest.opts)
1119 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1120 return pcTest.validateUser
1121 })
1122 pcTest.proxy.provider = &TestProvider{
1123 ValidToken: true,
1126 pcTest.validateUser = true
1128 pcTest.rw = httptest.NewRecorder()
1129 pcTest.req, _ = http.NewRequest("GET",
1130 pcTest.opts.ProxyPrefix+"/auth", nil)
1132 startSession := &sessions.SessionState{
1133 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: time.Now()}
1134 pcTest.SaveSession(startSession)
1136 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1137 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1138 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1139 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1142func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1143 var pcTest ProcessCookieTest
1145 pcTest.opts = options.NewOptions()
1146 pcTest.opts.SetXAuthRequest = true
1147 pcTest.opts.SetBasicAuth = true
1148 validation.Validate(pcTest.opts)
1150 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1151 return pcTest.validateUser
1152 })
1153 pcTest.proxy.provider = &TestProvider{
1154 ValidToken: true,
1157 pcTest.validateUser = true
1159 pcTest.rw = httptest.NewRecorder()
1160 pcTest.req, _ = http.NewRequest("GET",
1161 pcTest.opts.ProxyPrefix+"/auth", nil)
1163 startSession := &sessions.SessionState{
1164 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: time.Now()}
1165 pcTest.SaveSession(startSession)
1167 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1168 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1169 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1170 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1171 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:"+pcTest.opts.BasicAuthPassword))
1172 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1175func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1176 var pcTest ProcessCookieTest
1178 pcTest.opts = options.NewOptions()
1179 pcTest.opts.SetXAuthRequest = true
1180 pcTest.opts.SetBasicAuth = false
1181 validation.Validate(pcTest.opts)
1183 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1184 return pcTest.validateUser
1185 })
1186 pcTest.proxy.provider = &TestProvider{
1187 ValidToken: true,
1190 pcTest.validateUser = true
1192 pcTest.rw = httptest.NewRecorder()
1193 pcTest.req, _ = http.NewRequest("GET",
1194 pcTest.opts.ProxyPrefix+"/auth", nil)
1196 startSession := &sessions.SessionState{
1197 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: time.Now()}
1198 pcTest.SaveSession(startSession)
1200 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1201 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1202 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1203 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1204 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1207func TestAuthSkippedForPreflightRequests(t *testing.T) {
1208 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1209 w.WriteHeader(200)
1210 w.Write([]byte("response"))
1211 }))
1212 defer upstream.Close()
1214 opts := options.NewOptions()
1215 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1216 opts.ClientID = "aljsal"
1217 opts.ClientSecret = "jglkfsdgj"
1218 opts.Cookie.Secret = base64CookieSecret
1219 opts.SkipAuthPreflight = true
1220 validation.Validate(opts)
1222 upstreamURL, _ := url.Parse(upstream.URL)
1223 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1225 proxy := NewOAuthProxy(opts, func(string) bool { return false })
1226 rw := httptest.NewRecorder()
1227 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1228 proxy.ServeHTTP(rw, req)
1230 assert.Equal(t, 200, rw.Code)
1231 assert.Equal(t, "response", rw.Body.String())
1234type SignatureAuthenticator struct {
1235 auth hmacauth.HmacAuth
1238func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1239 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1240 if result == hmacauth.ResultNoSignature {
1241 w.Write([]byte("no signature received"))
1242 } else if result == hmacauth.ResultMatch {
1243 w.Write([]byte("signatures match"))
1244 } else if result == hmacauth.ResultMismatch {
1245 w.Write([]byte("signatures do not match:" +
1246 "\n received: " + headerSig +
1247 "\n computed: " + computedSig))
1248 } else {
1249 panic("Unknown result value: " + result.String())
1253type SignatureTest struct {
1254 opts *options.Options
1255 upstream *httptest.Server
1256 upstreamHost string
1257 provider *httptest.Server
1258 header http.Header
1259 rw *httptest.ResponseRecorder
1260 authenticator *SignatureAuthenticator
1263func NewSignatureTest() *SignatureTest {
1264 opts := options.NewOptions()
1265 opts.Cookie.Secret = rawCookieSecret
1266 opts.ClientID = "client ID"
1267 opts.ClientSecret = "client secret"
1268 opts.EmailDomains = []string{"acm.org"}
1270 authenticator := &SignatureAuthenticator{}
1271 upstream := httptest.NewServer(
1272 http.HandlerFunc(authenticator.Authenticate))
1273 upstreamURL, _ := url.Parse(upstream.URL)
1274 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1276 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1277 w.Write([]byte(`{"access_token": "my_auth_token"}`))
1279 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1280 providerURL, _ := url.Parse(provider.URL)
1281 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1283 return &SignatureTest{
1284 opts,
1285 upstream,
1286 upstreamURL.Host,
1287 provider,
1288 make(http.Header),
1289 httptest.NewRecorder(),
1290 authenticator,
1294func (st *SignatureTest) Close() {
1295 st.provider.Close()
1296 st.upstream.Close()
1299// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1300// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1301// https://github.com/18F/hmacauth/pull/4
1302type fakeNetConn struct {
1303 reqBody string
1306func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1307 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1308 copy(p, fnc.reqBody)
1309 fnc.reqBody = ""
1310 return bodyLen, io.EOF
1312 return 0, io.EOF
1315func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
1316 err := validation.Validate(st.opts)
1317 if err != nil {
1318 panic(err)
1320 proxy := NewOAuthProxy(st.opts, func(email string) bool { return true })
1322 var bodyBuf io.ReadCloser
1323 if body != "" {
1324 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1326 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1327 req.Header = st.header
1329 state := &sessions.SessionState{
1330 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1331 err = proxy.SaveSession(st.rw, req, state)
1332 if err != nil {
1333 panic(err)
1335 for _, c := range st.rw.Result().Cookies() {
1336 req.AddCookie(c)
1338 // This is used by the upstream to validate the signature.
1339 st.authenticator.auth = hmacauth.NewHmacAuth(
1340 crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
1341 proxy.ServeHTTP(st.rw, req)
1344func TestNoRequestSignature(t *testing.T) {
1345 st := NewSignatureTest()
1346 defer st.Close()
1347 st.MakeRequestWithExpectedKey("GET", "", "")
1348 assert.Equal(t, 200, st.rw.Code)
1349 assert.Equal(t, st.rw.Body.String(), "no signature received")
1352func TestRequestSignatureGetRequest(t *testing.T) {
1353 st := NewSignatureTest()
1354 defer st.Close()
1355 st.opts.SignatureKey = "sha1:7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d"
1356 st.MakeRequestWithExpectedKey("GET", "", "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d")
1357 assert.Equal(t, 200, st.rw.Code)
1358 assert.Equal(t, st.rw.Body.String(), "signatures match")
1361func TestRequestSignaturePostRequest(t *testing.T) {
1362 st := NewSignatureTest()
1363 defer st.Close()
1364 st.opts.SignatureKey = "sha1:d90df39e2d19282840252612dd7c81421a372f61"
1365 payload := `{ "hello": "world!" }`
1366 st.MakeRequestWithExpectedKey("POST", payload, "d90df39e2d19282840252612dd7c81421a372f61")
1367 assert.Equal(t, 200, st.rw.Code)
1368 assert.Equal(t, st.rw.Body.String(), "signatures match")
1371func TestGetRedirect(t *testing.T) {
1372 options := options.NewOptions()
1373 _ = validation.Validate(options)
1374 require.NotEmpty(t, options.ProxyPrefix)
1375 proxy := NewOAuthProxy(options, func(s string) bool { return false })
1377 tests := []struct {
1378 name string
1379 url string
1380 expectedRedirect string
1381 }{
1383 name: "request outside of ProxyPrefix redirects to original URL",
1384 url: "/foo/bar",
1385 expectedRedirect: "/foo/bar",
1386 },
1388 name: "request under ProxyPrefix redirects to root",
1389 url: proxy.ProxyPrefix + "/foo/bar",
1390 expectedRedirect: "/",
1391 },
1393 for _, tt := range tests {
1394 t.Run(tt.name, func(t *testing.T) {
1395 req, _ := http.NewRequest("GET", tt.url, nil)
1396 redirect, err := proxy.GetRedirect(req)
1398 assert.NoError(t, err)
1399 assert.Equal(t, tt.expectedRedirect, redirect)
1400 })
1404type ajaxRequestTest struct {
1405 opts *options.Options
1406 proxy *OAuthProxy
1409func newAjaxRequestTest() *ajaxRequestTest {
1410 test := &ajaxRequestTest{}
1411 test.opts = options.NewOptions()
1412 test.opts.Cookie.Secret = base64CookieSecret
1413 test.opts.ClientID = "gkljfdl"
1414 test.opts.ClientSecret = "sdflkjs"
1415 validation.Validate(test.opts)
1416 test.proxy = NewOAuthProxy(test.opts, func(email string) bool {
1417 return true
1418 })
1419 return test
1422func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1423 rw := httptest.NewRecorder()
1424 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1425 if err != nil {
1426 return 0, nil, err
1428 req.Header = header
1429 test.proxy.ServeHTTP(rw, req)
1430 return rw.Code, rw.Header(), nil
1433func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1434 test := newAjaxRequestTest()
1435 endpoint := "/test"
1437 code, rh, err := test.getEndpoint(endpoint, header)
1438 assert.NoError(t, err)
1439 assert.Equal(t, http.StatusUnauthorized, code)
1440 mime := rh.Get("Content-Type")
1441 assert.Equal(t, applicationJSON, mime)
1443func TestAjaxUnauthorizedRequest1(t *testing.T) {
1444 header := make(http.Header)
1445 header.Add("accept", applicationJSON)
1447 testAjaxUnauthorizedRequest(t, header)
1450func TestAjaxUnauthorizedRequest2(t *testing.T) {
1451 header := make(http.Header)
1452 header.Add("Accept", applicationJSON)
1454 testAjaxUnauthorizedRequest(t, header)
1457func TestAjaxForbiddendRequest(t *testing.T) {
1458 test := newAjaxRequestTest()
1459 endpoint := "/test"
1460 header := make(http.Header)
1461 code, rh, err := test.getEndpoint(endpoint, header)
1462 assert.NoError(t, err)
1463 assert.Equal(t, http.StatusForbidden, code)
1464 mime := rh.Get("Content-Type")
1465 assert.NotEqual(t, applicationJSON, mime)
1468func TestClearSplitCookie(t *testing.T) {
1469 opts := options.NewOptions()
1470 opts.Cookie.Name = "oauth2"
1471 opts.Cookie.Domains = []string{"abc"}
1472 store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1473 assert.Equal(t, err, nil)
1474 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1475 var rw = httptest.NewRecorder()
1476 req := httptest.NewRequest("get", "/", nil)
1478 req.AddCookie(&http.Cookie{
1479 Name: "test1",
1480 Value: "test1",
1481 })
1482 req.AddCookie(&http.Cookie{
1483 Name: "oauth2_0",
1484 Value: "oauth2_0",
1485 })
1486 req.AddCookie(&http.Cookie{
1487 Name: "oauth2_1",
1488 Value: "oauth2_1",
1489 })
1491 p.ClearSessionCookie(rw, req)
1492 header := rw.Header()
1494 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1497func TestClearSingleCookie(t *testing.T) {
1498 opts := options.NewOptions()
1499 opts.Cookie.Name = "oauth2"
1500 opts.Cookie.Domains = []string{"abc"}
1501 store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1502 assert.Equal(t, err, nil)
1503 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1504 var rw = httptest.NewRecorder()
1505 req := httptest.NewRequest("get", "/", nil)
1507 req.AddCookie(&http.Cookie{
1508 Name: "test1",
1509 Value: "test1",
1510 })
1511 req.AddCookie(&http.Cookie{
1512 Name: "oauth2",
1513 Value: "oauth2",
1514 })
1516 p.ClearSessionCookie(rw, req)
1517 header := rw.Header()
1519 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1522type NoOpKeySet struct {
1525func (NoOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
1526 splitStrings := strings.Split(jwt, ".")
1527 payloadString := splitStrings[1]
1528 return base64.RawURLEncoding.DecodeString(payloadString)
1531func TestGetJwtSession(t *testing.T) {
1532 /* token payload:
1534 "sub": "1234567890",
1535 "aud": "https://test.myapp.com",
1536 "name": "John Doe",
1537 "email": "john@example.com",
1538 "iss": "https://issuer.example.com",
1539 "iat": 1553691215,
1540 "exp": 1912151821
1542 */
1543 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1544 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1545 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1546 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1547 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1548 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1550 keyset := NoOpKeySet{}
1551 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1552 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1554 test := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1555 opts.PassAuthorization = true
1556 opts.SetAuthorization = true
1557 opts.SetXAuthRequest = true
1558 opts.SkipJwtBearerTokens = true
1559 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
1560 })
1561 tp, _ := test.proxy.provider.(*TestProvider)
1562 tp.GroupValidator = func(s string) bool {
1563 return true
1566 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1567 test.req.Header = map[string][]string{
1568 "Authorization": {authHeader},
1571 // Bearer
1572 session, _ := test.proxy.GetJwtSession(test.req)
1573 assert.Equal(t, session.User, "john@example.com")
1574 assert.Equal(t, session.Email, "john@example.com")
1575 assert.Equal(t, session.ExpiresOn, time.Unix(1912151821, 0))
1576 assert.Equal(t, session.IDToken, goodJwt)
1578 test.proxy.ServeHTTP(test.rw, test.req)
1579 if test.rw.Code >= 400 {
1580 t.Fatalf("expected 3xx got %d", test.rw.Code)
1583 // Check PassAuthorization, should overwrite Basic header
1584 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
1585 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "john@example.com")
1586 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
1588 // SetAuthorization and SetXAuthRequest
1589 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
1590 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "john@example.com")
1591 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
1594func TestFindJwtBearerToken(t *testing.T) {
1595 p := OAuthProxy{CookieName: "oauth2", CookieDomains: []string{"abc"}}
1596 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}}
1598 validToken := "eyJfoobar.eyJfoobar.12345asdf"
1599 var token string
1601 // Bearer
1602 getReq.Header = map[string][]string{
1603 "Authorization": {fmt.Sprintf("Bearer %s", validToken)},
1606 token, _ = p.findBearerToken(getReq)
1607 assert.Equal(t, validToken, token)
1609 // Basic - no password
1610 getReq.SetBasicAuth(token, "")
1611 token, _ = p.findBearerToken(getReq)
1612 assert.Equal(t, validToken, token)
1614 // Basic - sentinel password
1615 getReq.SetBasicAuth(token, "x-oauth-basic")
1616 token, _ = p.findBearerToken(getReq)
1617 assert.Equal(t, validToken, token)
1619 // Basic - any username, password matching jwt pattern
1620 getReq.SetBasicAuth("any-username-you-could-wish-for", token)
1621 token, _ = p.findBearerToken(getReq)
1622 assert.Equal(t, validToken, token)
1624 failures := []string{
1625 // Too many parts
1626 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1627 // Not enough parts
1628 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA",
1629 // Invalid encrypted key
1630 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.//////.dGVzdA.dGVzdA.dGVzdA",
1631 // Invalid IV
1632 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.//////.dGVzdA.dGVzdA",
1633 // Invalid ciphertext
1634 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.//////.dGVzdA",
1635 // Invalid tag
1636 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.//////",
1637 // Invalid header
1638 "W10.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1639 // Invalid header
1640 "######.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1641 // Missing alc/enc params
1642 "e30.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1645 for _, failure := range failures {
1646 getReq.Header = map[string][]string{
1647 "Authorization": {fmt.Sprintf("Bearer %s", failure)},
1649 _, err := p.findBearerToken(getReq)
1650 assert.Error(t, err)
1653 fmt.Printf("%s", token)
1656func Test_prepareNoCache(t *testing.T) {
1657 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1658 prepareNoCache(w)
1659 })
1660 mux := http.NewServeMux()
1661 mux.Handle("/", handler)
1663 rec := httptest.NewRecorder()
1664 req := httptest.NewRequest(http.MethodGet, "/", nil)
1665 mux.ServeHTTP(rec, req)
1667 for k, v := range noCacheHeaders {
1668 assert.Equal(t, rec.Header().Get(k), v)
1672func Test_noCacheHeadersDoesNotExistsInResponseHeadersFromUpstream(t *testing.T) {
1673 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1674 w.Write([]byte("upstream"))
1675 }))
1676 t.Cleanup(upstream.Close)
1678 opts := options.NewOptions()
1679 opts.Upstreams = []string{upstream.URL}
1680 opts.SkipAuthRegex = []string{".*"}
1681 _ = validation.Validate(opts)
1682 proxy := NewOAuthProxy(opts, func(email string) bool {
1683 return true
1684 })
1686 rec := httptest.NewRecorder()
1687 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
1688 proxy.ServeHTTP(rec, req)
1690 assert.Equal(t, http.StatusOK, rec.Code)
1691 assert.Equal(t, "upstream", rec.Body.String())
1693 // checking noCacheHeaders does not exists in response headers from upstream
1694 for k := range noCacheHeaders {
1695 assert.Equal(t, "", rec.Header().Get(k))