git.iko.soy/typeable/oauth2-proxy
on v5.1.0 help sign in
typeable/oauth2-proxy/oauthproxy_test.go
1526 lines 45 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 }
328 for _, tc := range testCases {
329 t.Run(tc.Desc, func(t *testing.T) {
330 result := proxy.IsValidRedirect(tc.Redirect)
332 if result != tc.ExpectedResult {
333 t.Errorf("expected %t got %t", tc.ExpectedResult, result)
334 }
335 })
336 }
339type TestProvider struct {
340 *providers.ProviderData
341 EmailAddress string
342 ValidToken bool
343 GroupValidator func(string) bool
346func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
347 return &TestProvider{
348 ProviderData: &providers.ProviderData{
349 ProviderName: "Test Provider",
350 LoginURL: &url.URL{
351 Scheme: "http",
352 Host: providerURL.Host,
353 Path: "/oauth/authorize",
354 },
355 RedeemURL: &url.URL{
356 Scheme: "http",
357 Host: providerURL.Host,
358 Path: "/oauth/token",
359 },
360 ProfileURL: &url.URL{
361 Scheme: "http",
362 Host: providerURL.Host,
363 Path: "/api/v1/profile",
364 },
365 Scope: "profile.email",
366 },
367 EmailAddress: emailAddress,
368 GroupValidator: func(s string) bool {
369 return true
370 },
371 }
374func (tp *TestProvider) GetEmailAddress(session *sessions.SessionState) (string, error) {
375 return tp.EmailAddress, nil
378func (tp *TestProvider) ValidateSessionState(session *sessions.SessionState) bool {
379 return tp.ValidToken
382func TestBasicAuthPassword(t *testing.T) {
383 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
384 logger.Printf("%#v", r)
385 var payload string
386 switch r.URL.Path {
387 case "/oauth/token":
388 payload = `{"access_token": "my_auth_token"}`
389 default:
390 payload = r.Header.Get("Authorization")
391 if payload == "" {
392 payload = "No Authorization header found."
393 }
394 }
395 w.WriteHeader(200)
396 w.Write([]byte(payload))
397 }))
398 opts := NewOptions()
399 opts.Upstreams = append(opts.Upstreams, providerServer.URL)
400 // The CookieSecret must be 32 bytes in order to create the AES
401 // cipher.
402 opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
403 opts.ClientID = "dlgkj"
404 opts.ClientSecret = "alkgret"
405 opts.CookieSecure = false
406 opts.PassBasicAuth = true
407 opts.PassUserHeaders = true
408 opts.PreferEmailToUser = true
409 opts.BasicAuthPassword = "This is a secure password"
410 opts.Validate()
412 providerURL, _ := url.Parse(providerServer.URL)
413 const emailAddress = "john.doe@example.com"
415 opts.provider = NewTestProvider(providerURL, emailAddress)
416 proxy := NewOAuthProxy(opts, func(email string) bool {
417 return email == emailAddress
418 })
420 rw := httptest.NewRecorder()
421 req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
422 strings.NewReader(""))
423 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
424 proxy.ServeHTTP(rw, req)
425 if rw.Code >= 400 {
426 t.Fatalf("expected 3xx got %d", rw.Code)
427 }
428 cookie := rw.HeaderMap["Set-Cookie"][1]
430 cookieName := proxy.CookieName
431 var value string
432 keyPrefix := cookieName + "="
434 for _, field := range strings.Split(cookie, "; ") {
435 value = strings.TrimPrefix(field, keyPrefix)
436 if value != field {
437 break
438 } else {
439 value = ""
440 }
441 }
443 req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
444 req.AddCookie(&http.Cookie{
445 Name: cookieName,
446 Value: value,
447 Path: "/",
448 Expires: time.Now().Add(time.Duration(24)),
449 HttpOnly: true,
450 })
451 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
453 rw = httptest.NewRecorder()
454 proxy.ServeHTTP(rw, req)
456 // The username in the basic auth credentials is expected to be equal to the email address from the
457 // auth response, so we use the same variable here.
458 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
459 assert.Equal(t, expectedHeader, rw.Body.String())
460 providerServer.Close()
463func TestBasicAuthWithEmail(t *testing.T) {
464 opts := NewOptions()
465 opts.PassBasicAuth = true
466 opts.PassUserHeaders = false
467 opts.PreferEmailToUser = false
468 opts.BasicAuthPassword = "This is a secure password"
469 opts.Validate()
471 const emailAddress = "john.doe@example.com"
472 const userName = "9fcab5c9b889a557"
474 // The username in the basic auth credentials is expected to be equal to the email address from the
475 expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
476 expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
478 session := &sessions.SessionState{
479 User: userName,
480 Email: emailAddress,
481 AccessToken: "oauth_token",
482 CreatedAt: time.Now(),
483 }
484 {
485 rw := httptest.NewRecorder()
486 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
487 proxy := NewOAuthProxy(opts, func(email string) bool {
488 return email == emailAddress
489 })
490 proxy.addHeadersForProxying(rw, req, session)
491 assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
492 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
493 }
495 opts.PreferEmailToUser = true
496 {
497 rw := httptest.NewRecorder()
498 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
500 proxy := NewOAuthProxy(opts, func(email string) bool {
501 return email == emailAddress
502 })
503 proxy.addHeadersForProxying(rw, req, session)
504 assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
505 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
506 }
509func TestPassUserHeadersWithEmail(t *testing.T) {
510 opts := NewOptions()
511 opts.PassBasicAuth = false
512 opts.PassUserHeaders = true
513 opts.PreferEmailToUser = false
514 opts.Validate()
516 const emailAddress = "john.doe@example.com"
517 const userName = "9fcab5c9b889a557"
519 session := &sessions.SessionState{
520 User: userName,
521 Email: emailAddress,
522 AccessToken: "oauth_token",
523 CreatedAt: time.Now(),
524 }
525 {
526 rw := httptest.NewRecorder()
527 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
528 proxy := NewOAuthProxy(opts, func(email string) bool {
529 return email == emailAddress
530 })
531 proxy.addHeadersForProxying(rw, req, session)
532 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
533 }
535 opts.PreferEmailToUser = true
536 {
537 rw := httptest.NewRecorder()
538 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
540 proxy := NewOAuthProxy(opts, func(email string) bool {
541 return email == emailAddress
542 })
543 proxy.addHeadersForProxying(rw, req, session)
544 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
545 }
548type PassAccessTokenTest struct {
549 providerServer *httptest.Server
550 proxy *OAuthProxy
551 opts *Options
554type PassAccessTokenTestOptions struct {
555 PassAccessToken bool
556 ProxyUpstream string
559func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
560 t := &PassAccessTokenTest{}
562 t.providerServer = httptest.NewServer(
563 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
564 var payload string
565 switch r.URL.Path {
566 case "/oauth/token":
567 payload = `{"access_token": "my_auth_token"}`
568 default:
569 payload = r.Header.Get("X-Forwarded-Access-Token")
570 if payload == "" {
571 payload = "No access token found."
572 }
573 }
574 w.WriteHeader(200)
575 w.Write([]byte(payload))
576 }))
578 t.opts = NewOptions()
579 t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL)
580 if opts.ProxyUpstream != "" {
581 t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream)
582 }
583 // The CookieSecret must be 32 bytes in order to create the AES
584 // cipher.
585 t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
586 t.opts.ClientID = "slgkj"
587 t.opts.ClientSecret = "gfjgojl"
588 t.opts.CookieSecure = false
589 t.opts.PassAccessToken = opts.PassAccessToken
590 t.opts.Validate()
592 providerURL, _ := url.Parse(t.providerServer.URL)
593 const emailAddress = "michael.bland@gsa.gov"
595 t.opts.provider = NewTestProvider(providerURL, emailAddress)
596 t.proxy = NewOAuthProxy(t.opts, func(email string) bool {
597 return email == emailAddress
598 })
599 return t
602func (patTest *PassAccessTokenTest) Close() {
603 patTest.providerServer.Close()
606func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
607 cookie string) {
608 rw := httptest.NewRecorder()
609 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
610 strings.NewReader(""))
611 if err != nil {
612 return 0, ""
613 }
614 req.AddCookie(patTest.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
615 patTest.proxy.ServeHTTP(rw, req)
616 return rw.Code, rw.HeaderMap["Set-Cookie"][1]
619// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
620// and cookie and returns body and status code.
621func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
622 cookieName := patTest.proxy.CookieName
623 var value string
624 keyPrefix := cookieName + "="
626 for _, field := range strings.Split(cookie, "; ") {
627 value = strings.TrimPrefix(field, keyPrefix)
628 if value != field {
629 break
630 } else {
631 value = ""
632 }
633 }
634 if value == "" {
635 return 0, ""
636 }
638 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
639 if err != nil {
640 return 0, ""
641 }
642 req.AddCookie(&http.Cookie{
643 Name: cookieName,
644 Value: value,
645 Path: "/",
646 Expires: time.Now().Add(time.Duration(24)),
647 HttpOnly: true,
648 })
650 rw := httptest.NewRecorder()
651 patTest.proxy.ServeHTTP(rw, req)
652 return rw.Code, rw.Body.String()
655func TestForwardAccessTokenUpstream(t *testing.T) {
656 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
657 PassAccessToken: true,
658 })
659 defer patTest.Close()
661 // A successful validation will redirect and set the auth cookie.
662 code, cookie := patTest.getCallbackEndpoint()
663 if code != 302 {
664 t.Fatalf("expected 302; got %d", code)
665 }
666 assert.NotEqual(t, nil, cookie)
668 // Now we make a regular request; the access_token from the cookie is
669 // forwarded as the "X-Forwarded-Access-Token" header. The token is
670 // read by the test provider server and written in the response body.
671 code, payload := patTest.getEndpointWithCookie(cookie, "/")
672 if code != 200 {
673 t.Fatalf("expected 200; got %d", code)
674 }
675 assert.Equal(t, "my_auth_token", payload)
678func TestStaticProxyUpstream(t *testing.T) {
679 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
680 PassAccessToken: true,
681 ProxyUpstream: "static://200/static-proxy",
682 })
684 defer patTest.Close()
686 // A successful validation will redirect and set the auth cookie.
687 code, cookie := patTest.getCallbackEndpoint()
688 if code != 302 {
689 t.Fatalf("expected 302; got %d", code)
690 }
691 assert.NotEqual(t, nil, cookie)
693 // Now we make a regular request againts the upstream proxy; And validate
694 // the returned status code through the static proxy.
695 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
696 if code != 200 {
697 t.Fatalf("expected 200; got %d", code)
698 }
699 assert.Equal(t, "Authenticated", payload)
702func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
703 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
704 PassAccessToken: false,
705 })
706 defer patTest.Close()
708 // A successful validation will redirect and set the auth cookie.
709 code, cookie := patTest.getCallbackEndpoint()
710 if code != 302 {
711 t.Fatalf("expected 302; got %d", code)
712 }
713 assert.NotEqual(t, nil, cookie)
715 // Now we make a regular request, but the access token header should
716 // not be present.
717 code, payload := patTest.getEndpointWithCookie(cookie, "/")
718 if code != 200 {
719 t.Fatalf("expected 200; got %d", code)
720 }
721 assert.Equal(t, "No access token found.", payload)
724type SignInPageTest struct {
725 opts *Options
726 proxy *OAuthProxy
727 signInRegexp *regexp.Regexp
728 signInProviderRegexp *regexp.Regexp
731const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
732const signInSkipProvider = `>Found<`
734func NewSignInPageTest(skipProvider bool) *SignInPageTest {
735 var sipTest SignInPageTest
737 sipTest.opts = NewOptions()
738 sipTest.opts.CookieSecret = "adklsj2"
739 sipTest.opts.ClientID = "lkdgj"
740 sipTest.opts.ClientSecret = "sgiufgoi"
741 sipTest.opts.SkipProviderButton = skipProvider
742 sipTest.opts.Validate()
744 sipTest.proxy = NewOAuthProxy(sipTest.opts, func(email string) bool {
745 return true
746 })
747 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
748 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
750 return &sipTest
753func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
754 rw := httptest.NewRecorder()
755 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
756 sipTest.proxy.ServeHTTP(rw, req)
757 return rw.Code, rw.Body.String()
760func TestSignInPageIncludesTargetRedirect(t *testing.T) {
761 sipTest := NewSignInPageTest(false)
762 const endpoint = "/some/random/endpoint"
764 code, body := sipTest.GetEndpoint(endpoint)
765 assert.Equal(t, 403, code)
767 match := sipTest.signInRegexp.FindStringSubmatch(body)
768 if match == nil {
769 t.Fatal("Did not find pattern in body: " +
770 signInRedirectPattern + "\nBody:\n" + body)
771 }
772 if match[1] != endpoint {
773 t.Fatal(`expected redirect to "` + endpoint +
774 `", but was "` + match[1] + `"`)
775 }
778func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
779 sipTest := NewSignInPageTest(false)
780 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
781 assert.Equal(t, 200, code)
783 match := sipTest.signInRegexp.FindStringSubmatch(body)
784 if match == nil {
785 t.Fatal("Did not find pattern in body: " +
786 signInRedirectPattern + "\nBody:\n" + body)
787 }
788 if match[1] != "/" {
789 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
790 }
793func TestSignInPageSkipProvider(t *testing.T) {
794 sipTest := NewSignInPageTest(true)
795 const endpoint = "/some/random/endpoint"
797 code, body := sipTest.GetEndpoint(endpoint)
798 assert.Equal(t, 302, code)
800 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
801 if match == nil {
802 t.Fatal("Did not find pattern in body: " +
803 signInSkipProvider + "\nBody:\n" + body)
804 }
807func TestSignInPageSkipProviderDirect(t *testing.T) {
808 sipTest := NewSignInPageTest(true)
809 const endpoint = "/sign_in"
811 code, body := sipTest.GetEndpoint(endpoint)
812 assert.Equal(t, 302, code)
814 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
815 if match == nil {
816 t.Fatal("Did not find pattern in body: " +
817 signInSkipProvider + "\nBody:\n" + body)
818 }
821type ProcessCookieTest struct {
822 opts *Options
823 proxy *OAuthProxy
824 rw *httptest.ResponseRecorder
825 req *http.Request
826 provider TestProvider
827 responseCode int
828 validateUser bool
831type ProcessCookieTestOpts struct {
832 providerValidateCookieResponse bool
835type OptionsModifier func(*Options)
837func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) *ProcessCookieTest {
838 var pcTest ProcessCookieTest
840 pcTest.opts = NewOptions()
841 for _, modifier := range modifiers {
842 modifier(pcTest.opts)
843 }
844 pcTest.opts.ClientID = "asdfljk"
845 pcTest.opts.ClientSecret = "lkjfdsig"
846 pcTest.opts.CookieSecret = "0123456789abcdefabcd"
847 // First, set the CookieRefresh option so proxy.AesCipher is created,
848 // needed to encrypt the access_token.
849 pcTest.opts.CookieRefresh = time.Hour
850 pcTest.opts.Validate()
852 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
853 return pcTest.validateUser
854 })
855 pcTest.proxy.provider = &TestProvider{
856 ValidToken: opts.providerValidateCookieResponse,
857 }
859 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
860 // access_token validation.
861 pcTest.proxy.CookieRefresh = time.Duration(0)
862 pcTest.rw = httptest.NewRecorder()
863 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
864 pcTest.validateUser = true
865 return &pcTest
868func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
869 return NewProcessCookieTest(ProcessCookieTestOpts{
870 providerValidateCookieResponse: true,
871 })
874func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) *ProcessCookieTest {
875 return NewProcessCookieTest(ProcessCookieTestOpts{
876 providerValidateCookieResponse: true,
877 }, modifiers...)
880func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
881 err := p.proxy.SaveSession(p.rw, p.req, s)
882 if err != nil {
883 return err
884 }
885 for _, cookie := range p.rw.Result().Cookies() {
886 p.req.AddCookie(cookie)
887 }
888 return nil
891func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
892 return p.proxy.LoadCookiedSession(p.req)
895func TestLoadCookiedSession(t *testing.T) {
896 pcTest := NewProcessCookieTestWithDefaults()
898 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: time.Now()}
899 pcTest.SaveSession(startSession)
901 session, err := pcTest.LoadCookiedSession()
902 assert.Equal(t, nil, err)
903 assert.Equal(t, startSession.Email, session.Email)
904 assert.Equal(t, "john.doe@example.com", session.User)
905 assert.Equal(t, startSession.AccessToken, session.AccessToken)
908func TestProcessCookieNoCookieError(t *testing.T) {
909 pcTest := NewProcessCookieTestWithDefaults()
911 session, err := pcTest.LoadCookiedSession()
912 assert.Equal(t, "Cookie \"_oauth2_proxy\" not present", err.Error())
913 if session != nil {
914 t.Errorf("expected nil session. got %#v", session)
915 }
918func TestProcessCookieRefreshNotSet(t *testing.T) {
919 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
920 opts.CookieExpire = time.Duration(23) * time.Hour
921 })
922 reference := time.Now().Add(time.Duration(-2) * time.Hour)
924 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
925 pcTest.SaveSession(startSession)
927 session, err := pcTest.LoadCookiedSession()
928 assert.Equal(t, nil, err)
929 if session.Age() < time.Duration(-2)*time.Hour {
930 t.Errorf("cookie too young %v", session.Age())
931 }
932 assert.Equal(t, startSession.Email, session.Email)
935func TestProcessCookieFailIfCookieExpired(t *testing.T) {
936 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
937 opts.CookieExpire = time.Duration(24) * time.Hour
938 })
939 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
940 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
941 pcTest.SaveSession(startSession)
943 session, err := pcTest.LoadCookiedSession()
944 assert.NotEqual(t, nil, err)
945 if session != nil {
946 t.Errorf("expected nil session %#v", session)
947 }
950func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
951 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
952 opts.CookieExpire = time.Duration(24) * time.Hour
953 })
954 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
955 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
956 pcTest.SaveSession(startSession)
958 pcTest.proxy.CookieRefresh = time.Hour
959 session, err := pcTest.LoadCookiedSession()
960 assert.NotEqual(t, nil, err)
961 if session != nil {
962 t.Errorf("expected nil session %#v", session)
963 }
966func NewUserInfoEndpointTest() *ProcessCookieTest {
967 pcTest := NewProcessCookieTestWithDefaults()
968 pcTest.req, _ = http.NewRequest("GET",
969 pcTest.opts.ProxyPrefix+"/userinfo", nil)
970 return pcTest
973func TestUserInfoEndpointAccepted(t *testing.T) {
974 test := NewUserInfoEndpointTest()
975 startSession := &sessions.SessionState{
976 Email: "john.doe@example.com", AccessToken: "my_access_token"}
977 test.SaveSession(startSession)
979 test.proxy.ServeHTTP(test.rw, test.req)
980 assert.Equal(t, http.StatusOK, test.rw.Code)
981 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
982 assert.Equal(t, "{\"email\":\"john.doe@example.com\"}\n", string(bodyBytes))
985func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
986 test := NewUserInfoEndpointTest()
988 test.proxy.ServeHTTP(test.rw, test.req)
989 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
992func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) *ProcessCookieTest {
993 pcTest := NewProcessCookieTestWithOptionsModifiers(modifiers...)
994 pcTest.req, _ = http.NewRequest("GET",
995 pcTest.opts.ProxyPrefix+"/auth", nil)
996 return pcTest
999func TestAuthOnlyEndpointAccepted(t *testing.T) {
1000 test := NewAuthOnlyEndpointTest()
1001 startSession := &sessions.SessionState{
1002 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
1003 test.SaveSession(startSession)
1005 test.proxy.ServeHTTP(test.rw, test.req)
1006 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1007 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1008 assert.Equal(t, "", string(bodyBytes))
1011func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1012 test := NewAuthOnlyEndpointTest()
1014 test.proxy.ServeHTTP(test.rw, test.req)
1015 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1016 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1017 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1020func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1021 test := NewAuthOnlyEndpointTest(func(opts *Options) {
1022 opts.CookieExpire = time.Duration(24) * time.Hour
1023 })
1024 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1025 startSession := &sessions.SessionState{
1026 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
1027 test.SaveSession(startSession)
1029 test.proxy.ServeHTTP(test.rw, test.req)
1030 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1031 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1032 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1035func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1036 test := NewAuthOnlyEndpointTest()
1037 startSession := &sessions.SessionState{
1038 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
1039 test.SaveSession(startSession)
1040 test.validateUser = false
1042 test.proxy.ServeHTTP(test.rw, test.req)
1043 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1044 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1045 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1048func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1049 var pcTest ProcessCookieTest
1051 pcTest.opts = NewOptions()
1052 pcTest.opts.SetXAuthRequest = true
1053 pcTest.opts.Validate()
1055 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1056 return pcTest.validateUser
1057 })
1058 pcTest.proxy.provider = &TestProvider{
1059 ValidToken: true,
1062 pcTest.validateUser = true
1064 pcTest.rw = httptest.NewRecorder()
1065 pcTest.req, _ = http.NewRequest("GET",
1066 pcTest.opts.ProxyPrefix+"/auth", nil)
1068 startSession := &sessions.SessionState{
1069 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: time.Now()}
1070 pcTest.SaveSession(startSession)
1072 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1073 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1074 assert.Equal(t, "oauth_user", pcTest.rw.HeaderMap["X-Auth-Request-User"][0])
1075 assert.Equal(t, "oauth_user@example.com", pcTest.rw.HeaderMap["X-Auth-Request-Email"][0])
1078func TestAuthSkippedForPreflightRequests(t *testing.T) {
1079 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1080 w.WriteHeader(200)
1081 w.Write([]byte("response"))
1082 }))
1083 defer upstream.Close()
1085 opts := NewOptions()
1086 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1087 opts.ClientID = "aljsal"
1088 opts.ClientSecret = "jglkfsdgj"
1089 opts.CookieSecret = "dkfjgdls"
1090 opts.SkipAuthPreflight = true
1091 opts.Validate()
1093 upstreamURL, _ := url.Parse(upstream.URL)
1094 opts.provider = NewTestProvider(upstreamURL, "")
1096 proxy := NewOAuthProxy(opts, func(string) bool { return false })
1097 rw := httptest.NewRecorder()
1098 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1099 proxy.ServeHTTP(rw, req)
1101 assert.Equal(t, 200, rw.Code)
1102 assert.Equal(t, "response", rw.Body.String())
1105type SignatureAuthenticator struct {
1106 auth hmacauth.HmacAuth
1109func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1110 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1111 if result == hmacauth.ResultNoSignature {
1112 w.Write([]byte("no signature received"))
1113 } else if result == hmacauth.ResultMatch {
1114 w.Write([]byte("signatures match"))
1115 } else if result == hmacauth.ResultMismatch {
1116 w.Write([]byte("signatures do not match:" +
1117 "\n received: " + headerSig +
1118 "\n computed: " + computedSig))
1119 } else {
1120 panic("Unknown result value: " + result.String())
1124type SignatureTest struct {
1125 opts *Options
1126 upstream *httptest.Server
1127 upstreamHost string
1128 provider *httptest.Server
1129 header http.Header
1130 rw *httptest.ResponseRecorder
1131 authenticator *SignatureAuthenticator
1134func NewSignatureTest() *SignatureTest {
1135 opts := NewOptions()
1136 opts.CookieSecret = "cookie secret"
1137 opts.ClientID = "client ID"
1138 opts.ClientSecret = "client secret"
1139 opts.EmailDomains = []string{"acm.org"}
1141 authenticator := &SignatureAuthenticator{}
1142 upstream := httptest.NewServer(
1143 http.HandlerFunc(authenticator.Authenticate))
1144 upstreamURL, _ := url.Parse(upstream.URL)
1145 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1147 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1148 w.Write([]byte(`{"access_token": "my_auth_token"}`))
1150 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1151 providerURL, _ := url.Parse(provider.URL)
1152 opts.provider = NewTestProvider(providerURL, "mbland@acm.org")
1154 return &SignatureTest{
1155 opts,
1156 upstream,
1157 upstreamURL.Host,
1158 provider,
1159 make(http.Header),
1160 httptest.NewRecorder(),
1161 authenticator,
1165func (st *SignatureTest) Close() {
1166 st.provider.Close()
1167 st.upstream.Close()
1170// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1171// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1172// https://github.com/18F/hmacauth/pull/4
1173type fakeNetConn struct {
1174 reqBody string
1177func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1178 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1179 copy(p, fnc.reqBody)
1180 fnc.reqBody = ""
1181 return bodyLen, io.EOF
1183 return 0, io.EOF
1186func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
1187 err := st.opts.Validate()
1188 if err != nil {
1189 panic(err)
1191 proxy := NewOAuthProxy(st.opts, func(email string) bool { return true })
1193 var bodyBuf io.ReadCloser
1194 if body != "" {
1195 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1197 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1198 req.Header = st.header
1200 state := &sessions.SessionState{
1201 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1202 err = proxy.SaveSession(st.rw, req, state)
1203 if err != nil {
1204 panic(err)
1206 for _, c := range st.rw.Result().Cookies() {
1207 req.AddCookie(c)
1209 // This is used by the upstream to validate the signature.
1210 st.authenticator.auth = hmacauth.NewHmacAuth(
1211 crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
1212 proxy.ServeHTTP(st.rw, req)
1215func TestNoRequestSignature(t *testing.T) {
1216 st := NewSignatureTest()
1217 defer st.Close()
1218 st.MakeRequestWithExpectedKey("GET", "", "")
1219 assert.Equal(t, 200, st.rw.Code)
1220 assert.Equal(t, st.rw.Body.String(), "no signature received")
1223func TestRequestSignatureGetRequest(t *testing.T) {
1224 st := NewSignatureTest()
1225 defer st.Close()
1226 st.opts.SignatureKey = "sha1:7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d"
1227 st.MakeRequestWithExpectedKey("GET", "", "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d")
1228 assert.Equal(t, 200, st.rw.Code)
1229 assert.Equal(t, st.rw.Body.String(), "signatures match")
1232func TestRequestSignaturePostRequest(t *testing.T) {
1233 st := NewSignatureTest()
1234 defer st.Close()
1235 st.opts.SignatureKey = "sha1:d90df39e2d19282840252612dd7c81421a372f61"
1236 payload := `{ "hello": "world!" }`
1237 st.MakeRequestWithExpectedKey("POST", payload, "d90df39e2d19282840252612dd7c81421a372f61")
1238 assert.Equal(t, 200, st.rw.Code)
1239 assert.Equal(t, st.rw.Body.String(), "signatures match")
1242func TestGetRedirect(t *testing.T) {
1243 options := NewOptions()
1244 _ = options.Validate()
1245 require.NotEmpty(t, options.ProxyPrefix)
1246 proxy := NewOAuthProxy(options, func(s string) bool { return false })
1248 tests := []struct {
1249 name string
1250 url string
1251 expectedRedirect string
1252 }{
1254 name: "request outside of ProxyPrefix redirects to original URL",
1255 url: "/foo/bar",
1256 expectedRedirect: "/foo/bar",
1257 },
1259 name: "request under ProxyPrefix redirects to root",
1260 url: proxy.ProxyPrefix + "/foo/bar",
1261 expectedRedirect: "/",
1262 },
1264 for _, tt := range tests {
1265 t.Run(tt.name, func(t *testing.T) {
1266 req, _ := http.NewRequest("GET", tt.url, nil)
1267 redirect, err := proxy.GetRedirect(req)
1269 assert.NoError(t, err)
1270 assert.Equal(t, tt.expectedRedirect, redirect)
1271 })
1275type ajaxRequestTest struct {
1276 opts *Options
1277 proxy *OAuthProxy
1280func newAjaxRequestTest() *ajaxRequestTest {
1281 test := &ajaxRequestTest{}
1282 test.opts = NewOptions()
1283 test.opts.CookieSecret = "sdflsw"
1284 test.opts.ClientID = "gkljfdl"
1285 test.opts.ClientSecret = "sdflkjs"
1286 test.opts.Validate()
1287 test.proxy = NewOAuthProxy(test.opts, func(email string) bool {
1288 return true
1289 })
1290 return test
1293func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1294 rw := httptest.NewRecorder()
1295 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1296 if err != nil {
1297 return 0, nil, err
1299 req.Header = header
1300 test.proxy.ServeHTTP(rw, req)
1301 return rw.Code, rw.Header(), nil
1304func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1305 test := newAjaxRequestTest()
1306 endpoint := "/test"
1308 code, rh, err := test.getEndpoint(endpoint, header)
1309 assert.NoError(t, err)
1310 assert.Equal(t, http.StatusUnauthorized, code)
1311 mime := rh.Get("Content-Type")
1312 assert.Equal(t, applicationJSON, mime)
1314func TestAjaxUnauthorizedRequest1(t *testing.T) {
1315 header := make(http.Header)
1316 header.Add("accept", applicationJSON)
1318 testAjaxUnauthorizedRequest(t, header)
1321func TestAjaxUnauthorizedRequest2(t *testing.T) {
1322 header := make(http.Header)
1323 header.Add("Accept", applicationJSON)
1325 testAjaxUnauthorizedRequest(t, header)
1328func TestAjaxForbiddendRequest(t *testing.T) {
1329 test := newAjaxRequestTest()
1330 endpoint := "/test"
1331 header := make(http.Header)
1332 code, rh, err := test.getEndpoint(endpoint, header)
1333 assert.NoError(t, err)
1334 assert.Equal(t, http.StatusForbidden, code)
1335 mime := rh.Get("Content-Type")
1336 assert.NotEqual(t, applicationJSON, mime)
1339func TestClearSplitCookie(t *testing.T) {
1340 opts := NewOptions()
1341 opts.CookieName = "oauth2"
1342 opts.CookieDomain = "abc"
1343 store, err := cookie.NewCookieSessionStore(&opts.SessionOptions, &opts.CookieOptions)
1344 assert.Equal(t, err, nil)
1345 p := OAuthProxy{CookieName: opts.CookieName, CookieDomain: opts.CookieDomain, sessionStore: store}
1346 var rw = httptest.NewRecorder()
1347 req := httptest.NewRequest("get", "/", nil)
1349 req.AddCookie(&http.Cookie{
1350 Name: "test1",
1351 Value: "test1",
1352 })
1353 req.AddCookie(&http.Cookie{
1354 Name: "oauth2_0",
1355 Value: "oauth2_0",
1356 })
1357 req.AddCookie(&http.Cookie{
1358 Name: "oauth2_1",
1359 Value: "oauth2_1",
1360 })
1362 p.ClearSessionCookie(rw, req)
1363 header := rw.Header()
1365 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1368func TestClearSingleCookie(t *testing.T) {
1369 opts := NewOptions()
1370 opts.CookieName = "oauth2"
1371 opts.CookieDomain = "abc"
1372 store, err := cookie.NewCookieSessionStore(&opts.SessionOptions, &opts.CookieOptions)
1373 assert.Equal(t, err, nil)
1374 p := OAuthProxy{CookieName: opts.CookieName, CookieDomain: opts.CookieDomain, sessionStore: store}
1375 var rw = httptest.NewRecorder()
1376 req := httptest.NewRequest("get", "/", nil)
1378 req.AddCookie(&http.Cookie{
1379 Name: "test1",
1380 Value: "test1",
1381 })
1382 req.AddCookie(&http.Cookie{
1383 Name: "oauth2",
1384 Value: "oauth2",
1385 })
1387 p.ClearSessionCookie(rw, req)
1388 header := rw.Header()
1390 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1393type NoOpKeySet struct {
1396func (NoOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
1397 splitStrings := strings.Split(jwt, ".")
1398 payloadString := splitStrings[1]
1399 jsonString, err := base64.RawURLEncoding.DecodeString(payloadString)
1400 return []byte(jsonString), err
1403func TestGetJwtSession(t *testing.T) {
1404 /* token payload:
1406 "sub": "1234567890",
1407 "aud": "https://test.myapp.com",
1408 "name": "John Doe",
1409 "email": "john@example.com",
1410 "iss": "https://issuer.example.com",
1411 "iat": 1553691215,
1412 "exp": 1912151821
1414 */
1415 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1416 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1417 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1418 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1419 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1420 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1422 keyset := NoOpKeySet{}
1423 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1424 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1426 test := NewAuthOnlyEndpointTest(func(opts *Options) {
1427 opts.PassAuthorization = true
1428 opts.SetAuthorization = true
1429 opts.SetXAuthRequest = true
1430 opts.SkipJwtBearerTokens = true
1431 opts.jwtBearerVerifiers = append(opts.jwtBearerVerifiers, verifier)
1432 })
1433 tp, _ := test.proxy.provider.(*TestProvider)
1434 tp.GroupValidator = func(s string) bool {
1435 return true
1438 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1439 test.req.Header = map[string][]string{
1440 "Authorization": {authHeader},
1443 // Bearer
1444 session, _ := test.proxy.GetJwtSession(test.req)
1445 assert.Equal(t, session.User, "john@example.com")
1446 assert.Equal(t, session.Email, "john@example.com")
1447 assert.Equal(t, session.ExpiresOn, time.Unix(1912151821, 0))
1448 assert.Equal(t, session.IDToken, goodJwt)
1450 test.proxy.ServeHTTP(test.rw, test.req)
1451 if test.rw.Code >= 400 {
1452 t.Fatalf("expected 3xx got %d", test.rw.Code)
1455 // Check PassAuthorization, should overwrite Basic header
1456 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
1457 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "john@example.com")
1458 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
1460 // SetAuthorization and SetXAuthRequest
1461 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
1462 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "john@example.com")
1463 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
1466func TestFindJwtBearerToken(t *testing.T) {
1467 p := OAuthProxy{CookieName: "oauth2", CookieDomain: "abc"}
1468 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}}
1470 validToken := "eyJfoobar.eyJfoobar.12345asdf"
1471 var token string
1473 // Bearer
1474 getReq.Header = map[string][]string{
1475 "Authorization": {fmt.Sprintf("Bearer %s", validToken)},
1478 token, _ = p.findBearerToken(getReq)
1479 assert.Equal(t, validToken, token)
1481 // Basic - no password
1482 getReq.SetBasicAuth(token, "")
1483 token, _ = p.findBearerToken(getReq)
1484 assert.Equal(t, validToken, token)
1486 // Basic - sentinel password
1487 getReq.SetBasicAuth(token, "x-oauth-basic")
1488 token, _ = p.findBearerToken(getReq)
1489 assert.Equal(t, validToken, token)
1491 // Basic - any username, password matching jwt pattern
1492 getReq.SetBasicAuth("any-username-you-could-wish-for", token)
1493 token, _ = p.findBearerToken(getReq)
1494 assert.Equal(t, validToken, token)
1496 failures := []string{
1497 // Too many parts
1498 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1499 // Not enough parts
1500 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA",
1501 // Invalid encrypted key
1502 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.//////.dGVzdA.dGVzdA.dGVzdA",
1503 // Invalid IV
1504 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.//////.dGVzdA.dGVzdA",
1505 // Invalid ciphertext
1506 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.//////.dGVzdA",
1507 // Invalid tag
1508 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.//////",
1509 // Invalid header
1510 "W10.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1511 // Invalid header
1512 "######.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1513 // Missing alc/enc params
1514 "e30.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1517 for _, failure := range failures {
1518 getReq.Header = map[string][]string{
1519 "Authorization": {fmt.Sprintf("Bearer %s", failure)},
1521 _, err := p.findBearerToken(getReq)
1522 assert.Error(t, err)
1525 fmt.Printf("%s", token)