git.iko.soy/typeable/oauth2-proxy
on request-rules help sign in
typeable/oauth2-proxy/oauthproxy_test.go
2356 lines 65 kB history raw
1package main
3import (
4 "bufio"
5 "context"
6 "crypto"
7 "encoding/base64"
8 "fmt"
9 "io"
10 "io/ioutil"
11 "net"
12 "net/http"
13 "net/http/httptest"
14 "net/url"
15 "os"
16 "regexp"
17 "strings"
18 "testing"
19 "time"
21 "github.com/coreos/go-oidc"
22 "github.com/mbland/hmacauth"
23 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
24 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
25 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
26 sessionscookie "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie"
27 "github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
28 "github.com/oauth2-proxy/oauth2-proxy/providers"
29 "github.com/stretchr/testify/assert"
30 "github.com/stretchr/testify/require"
31 "golang.org/x/net/websocket"
34const (
35 // The rawCookieSecret is 32 bytes and the base64CookieSecret is the base64
36 // encoded version of this.
37 rawCookieSecret = "secretthirtytwobytes+abcdefghijk"
38 base64CookieSecret = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpams"
39 clientID = "3984n253984d7348dm8234yf982t"
40 clientSecret = "gv3498mfc9t23y23974dm2394dm9"
43func init() {
44 logger.SetFlags(logger.Lshortfile)
47type WebSocketOrRestHandler struct {
48 restHandler http.Handler
49 wsHandler http.Handler
52func (h *WebSocketOrRestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
53 if r.Header.Get("Upgrade") == "websocket" {
54 h.wsHandler.ServeHTTP(w, r)
55 } else {
56 h.restHandler.ServeHTTP(w, r)
57 }
60func TestWebSocketProxy(t *testing.T) {
61 handler := WebSocketOrRestHandler{
62 restHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
63 w.WriteHeader(200)
64 hostname, _, _ := net.SplitHostPort(r.Host)
65 _, err := w.Write([]byte(hostname))
66 if err != nil {
67 t.Fatal(err)
68 }
69 }),
70 wsHandler: websocket.Handler(func(ws *websocket.Conn) {
71 defer func(t *testing.T) {
72 if err := ws.Close(); err != nil {
73 t.Fatal(err)
74 }
75 }(t)
76 var data []byte
77 err := websocket.Message.Receive(ws, &data)
78 if err != nil {
79 t.Fatal(err)
80 }
81 err = websocket.Message.Send(ws, data)
82 if err != nil {
83 t.Fatal(err)
84 }
85 }),
86 }
87 backend := httptest.NewServer(&handler)
88 t.Cleanup(backend.Close)
90 backendURL, _ := url.Parse(backend.URL)
92 opts := baseTestOptions()
93 var auth hmacauth.HmacAuth
94 opts.PassHostHeader = true
95 proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, opts, auth)
96 frontend := httptest.NewServer(proxyHandler)
97 t.Cleanup(frontend.Close)
99 frontendURL, _ := url.Parse(frontend.URL)
100 frontendWSURL := "ws://" + frontendURL.Host + "/"
102 ws, err := websocket.Dial(frontendWSURL, "", "http://localhost/")
103 if err != nil {
104 t.Fatal(err)
105 }
106 request := []byte("hello, world!")
107 err = websocket.Message.Send(ws, request)
108 if err != nil {
109 t.Fatal(err)
110 }
111 var response = make([]byte, 1024)
112 err = websocket.Message.Receive(ws, &response)
113 if err != nil {
114 t.Fatal(err)
115 }
116 if g, e := string(request), string(response); g != e {
117 t.Errorf("got body %q; expected %q", g, e)
118 }
120 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
121 res, _ := http.DefaultClient.Do(getReq)
122 bodyBytes, _ := ioutil.ReadAll(res.Body)
123 backendHostname, _, _ := net.SplitHostPort(backendURL.Host)
124 if g, e := string(bodyBytes), backendHostname; g != e {
125 t.Errorf("got body %q; expected %q", g, e)
126 }
129func TestNewReverseProxy(t *testing.T) {
130 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
131 w.WriteHeader(200)
132 hostname, _, _ := net.SplitHostPort(r.Host)
133 _, err := w.Write([]byte(hostname))
134 if err != nil {
135 t.Fatal(err)
136 }
137 }))
138 t.Cleanup(backend.Close)
140 backendURL, _ := url.Parse(backend.URL)
141 backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host)
142 backendHost := net.JoinHostPort(backendHostname, backendPort)
143 proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
145 proxyHandler := NewReverseProxy(proxyURL, &options.Options{FlushInterval: time.Second})
146 setProxyUpstreamHostHeader(proxyHandler, proxyURL)
147 frontend := httptest.NewServer(proxyHandler)
148 t.Cleanup(frontend.Close)
150 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
151 res, _ := http.DefaultClient.Do(getReq)
152 bodyBytes, _ := ioutil.ReadAll(res.Body)
153 if g, e := string(bodyBytes), backendHostname; g != e {
154 t.Errorf("got body %q; expected %q", g, e)
155 }
158func TestEncodedSlashes(t *testing.T) {
159 var seen string
160 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
161 w.WriteHeader(200)
162 seen = r.RequestURI
163 }))
164 t.Cleanup(backend.Close)
166 b, _ := url.Parse(backend.URL)
167 proxyHandler := NewReverseProxy(b, &options.Options{FlushInterval: time.Second})
168 setProxyDirector(proxyHandler)
169 frontend := httptest.NewServer(proxyHandler)
170 t.Cleanup(frontend.Close)
172 f, _ := url.Parse(frontend.URL)
173 encodedPath := "/a%2Fb/?c=1"
174 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: f.Host, Opaque: encodedPath}}
175 _, err := http.DefaultClient.Do(getReq)
176 if err != nil {
177 t.Fatal(err)
178 }
179 if seen != encodedPath {
180 t.Errorf("got bad request %q expected %q", seen, encodedPath)
181 }
184func TestRobotsTxt(t *testing.T) {
185 opts := baseTestOptions()
186 err := validation.Validate(opts)
187 assert.NoError(t, err)
189 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
190 if err != nil {
191 t.Fatal(err)
192 }
193 rw := httptest.NewRecorder()
194 req, _ := http.NewRequest("GET", "/robots.txt", nil)
195 proxy.ServeHTTP(rw, req)
196 assert.Equal(t, 200, rw.Code)
197 assert.Equal(t, "User-agent: *\nDisallow: /", rw.Body.String())
200func TestIsValidRedirect(t *testing.T) {
201 opts := baseTestOptions()
202 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
203 opts.WhitelistDomains = []string{
204 "foo.bar",
205 ".bar.foo",
206 "port.bar:8080",
207 ".sub.port.bar:8080",
208 "anyport.bar:*",
209 ".sub.anyport.bar:*",
210 }
211 err := validation.Validate(opts)
212 assert.NoError(t, err)
214 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
215 if err != nil {
216 t.Fatal(err)
217 }
219 testCases := []struct {
220 Desc, Redirect string
221 ExpectedResult bool
222 }{
223 {
224 Desc: "noRD",
225 Redirect: "",
226 ExpectedResult: false,
227 },
228 {
229 Desc: "singleSlash",
230 Redirect: "/redirect",
231 ExpectedResult: true,
232 },
233 {
234 Desc: "doubleSlash",
235 Redirect: "//redirect",
236 ExpectedResult: false,
237 },
238 {
239 Desc: "validHTTP",
240 Redirect: "http://foo.bar/redirect",
241 ExpectedResult: true,
242 },
243 {
244 Desc: "validHTTPS",
245 Redirect: "https://foo.bar/redirect",
246 ExpectedResult: true,
247 },
248 {
249 Desc: "invalidHTTPSubdomain",
250 Redirect: "http://baz.foo.bar/redirect",
251 ExpectedResult: false,
252 },
253 {
254 Desc: "invalidHTTPSSubdomain",
255 Redirect: "https://baz.foo.bar/redirect",
256 ExpectedResult: false,
257 },
258 {
259 Desc: "validHTTPSubdomain",
260 Redirect: "http://baz.bar.foo/redirect",
261 ExpectedResult: true,
262 },
263 {
264 Desc: "validHTTPSSubdomain",
265 Redirect: "https://baz.bar.foo/redirect",
266 ExpectedResult: true,
267 },
268 {
269 Desc: "validHTTPDomain",
270 Redirect: "http://bar.foo/redirect",
271 ExpectedResult: true,
272 },
273 {
274 Desc: "invalidHTTP1",
275 Redirect: "http://foo.bar.evil.corp/redirect",
276 ExpectedResult: false,
277 },
278 {
279 Desc: "invalidHTTPS1",
280 Redirect: "https://foo.bar.evil.corp/redirect",
281 ExpectedResult: false,
282 },
283 {
284 Desc: "invalidHTTP2",
285 Redirect: "http://evil.corp/redirect?rd=foo.bar",
286 ExpectedResult: false,
287 },
288 {
289 Desc: "invalidHTTPS2",
290 Redirect: "https://evil.corp/redirect?rd=foo.bar",
291 ExpectedResult: false,
292 },
293 {
294 Desc: "invalidPort",
295 Redirect: "https://evil.corp:3838/redirect",
296 ExpectedResult: false,
297 },
298 {
299 Desc: "invalidEmptyPort",
300 Redirect: "http://foo.bar:3838/redirect",
301 ExpectedResult: false,
302 },
303 {
304 Desc: "invalidEmptyPortSubdomain",
305 Redirect: "http://baz.bar.foo:3838/redirect",
306 ExpectedResult: false,
307 },
308 {
309 Desc: "validSpecificPort",
310 Redirect: "http://port.bar:8080/redirect",
311 ExpectedResult: true,
312 },
313 {
314 Desc: "invalidSpecificPort",
315 Redirect: "http://port.bar:3838/redirect",
316 ExpectedResult: false,
317 },
318 {
319 Desc: "validSpecificPortSubdomain",
320 Redirect: "http://foo.sub.port.bar:8080/redirect",
321 ExpectedResult: true,
322 },
323 {
324 Desc: "invalidSpecificPortSubdomain",
325 Redirect: "http://foo.sub.port.bar:3838/redirect",
326 ExpectedResult: false,
327 },
328 {
329 Desc: "validAnyPort1",
330 Redirect: "http://anyport.bar:8080/redirect",
331 ExpectedResult: true,
332 },
333 {
334 Desc: "validAnyPort2",
335 Redirect: "http://anyport.bar:8081/redirect",
336 ExpectedResult: true,
337 },
338 {
339 Desc: "validAnyPortSubdomain1",
340 Redirect: "http://a.sub.anyport.bar:8080/redirect",
341 ExpectedResult: true,
342 },
343 {
344 Desc: "validAnyPortSubdomain2",
345 Redirect: "http://a.sub.anyport.bar:8081/redirect",
346 ExpectedResult: true,
347 },
348 {
349 Desc: "openRedirect1",
350 Redirect: "/\\evil.com",
351 ExpectedResult: false,
352 },
353 {
354 Desc: "openRedirectSpace1",
355 Redirect: "/ /evil.com",
356 ExpectedResult: false,
357 },
358 {
359 Desc: "openRedirectSpace2",
360 Redirect: "/ \\evil.com",
361 ExpectedResult: false,
362 },
363 {
364 Desc: "openRedirectTab1",
365 Redirect: "/\t/evil.com",
366 ExpectedResult: false,
367 },
368 {
369 Desc: "openRedirectTab2",
370 Redirect: "/\t\\evil.com",
371 ExpectedResult: false,
372 },
373 {
374 Desc: "openRedirectVerticalTab1",
375 Redirect: "/\v/evil.com",
376 ExpectedResult: false,
377 },
378 {
379 Desc: "openRedirectVerticalTab2",
380 Redirect: "/\v\\evil.com",
381 ExpectedResult: false,
382 },
383 {
384 Desc: "openRedirectNewLine1",
385 Redirect: "/\n/evil.com",
386 ExpectedResult: false,
387 },
388 {
389 Desc: "openRedirectNewLine2",
390 Redirect: "/\n\\evil.com",
391 ExpectedResult: false,
392 },
393 {
394 Desc: "openRedirectCarriageReturn1",
395 Redirect: "/\r/evil.com",
396 ExpectedResult: false,
397 },
398 {
399 Desc: "openRedirectCarriageReturn2",
400 Redirect: "/\r\\evil.com",
401 ExpectedResult: false,
402 },
403 {
404 Desc: "openRedirectTripleTab",
405 Redirect: "/\t\t/\t/evil.com",
406 ExpectedResult: false,
407 },
408 {
409 Desc: "openRedirectTripleTab2",
410 Redirect: "/\t\t\\\t/evil.com",
411 ExpectedResult: false,
412 },
413 {
414 Desc: "openRedirectQuadTab1",
415 Redirect: "/\t\t/\t\t\\evil.com",
416 ExpectedResult: false,
417 },
418 {
419 Desc: "openRedirectQuadTab2",
420 Redirect: "/\t\t\\\t\t/evil.com",
421 ExpectedResult: false,
422 },
423 {
424 Desc: "openRedirectPeriod1",
425 Redirect: "/./\\evil.com",
426 ExpectedResult: false,
427 },
428 {
429 Desc: "openRedirectPeriod2",
430 Redirect: "/./../../\\evil.com",
431 ExpectedResult: false,
432 },
433 {
434 Desc: "openRedirectDoubleTab",
435 Redirect: "/\t/\t\\evil.com",
436 ExpectedResult: false,
437 },
438 }
440 for _, tc := range testCases {
441 t.Run(tc.Desc, func(t *testing.T) {
442 result := proxy.IsValidRedirect(tc.Redirect)
444 if result != tc.ExpectedResult {
445 t.Errorf("expected %t got %t", tc.ExpectedResult, result)
446 }
447 })
448 }
451func TestOpenRedirects(t *testing.T) {
452 opts := baseTestOptions()
453 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
454 opts.WhitelistDomains = []string{
455 "foo.bar",
456 ".bar.foo",
457 "port.bar:8080",
458 ".sub.port.bar:8080",
459 "anyport.bar:*",
460 ".sub.anyport.bar:*",
461 "www.whitelisteddomain.tld",
462 }
463 err := validation.Validate(opts)
464 assert.NoError(t, err)
466 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
467 if err != nil {
468 t.Fatal(err)
469 }
471 file, err := os.Open("./test/openredirects.txt")
472 if err != nil {
473 t.Fatal(err)
474 }
475 defer func(t *testing.T) {
476 if err := file.Close(); err != nil {
477 t.Fatal(err)
478 }
479 }(t)
481 scanner := bufio.NewScanner(file)
482 for scanner.Scan() {
483 rd := scanner.Text()
484 t.Run(rd, func(t *testing.T) {
485 rdUnescaped, err := url.QueryUnescape(rd)
486 if err != nil {
487 t.Fatal(err)
488 }
489 if proxy.IsValidRedirect(rdUnescaped) {
490 t.Errorf("Expected %q to not be valid (unescaped: %q)", rd, rdUnescaped)
491 }
492 })
493 }
495 if err := scanner.Err(); err != nil {
496 t.Fatal(err)
497 }
500type TestProvider struct {
501 *providers.ProviderData
502 EmailAddress string
503 ValidToken bool
504 GroupValidator func(string) bool
507var _ providers.Provider = (*TestProvider)(nil)
509func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
510 return &TestProvider{
511 ProviderData: &providers.ProviderData{
512 ProviderName: "Test Provider",
513 LoginURL: &url.URL{
514 Scheme: "http",
515 Host: providerURL.Host,
516 Path: "/oauth/authorize",
517 },
518 RedeemURL: &url.URL{
519 Scheme: "http",
520 Host: providerURL.Host,
521 Path: "/oauth/token",
522 },
523 ProfileURL: &url.URL{
524 Scheme: "http",
525 Host: providerURL.Host,
526 Path: "/api/v1/profile",
527 },
528 Scope: "profile.email",
529 },
530 EmailAddress: emailAddress,
531 GroupValidator: func(s string) bool {
532 return true
533 },
534 }
537func (tp *TestProvider) GetEmailAddress(ctx context.Context, session *sessions.SessionState) (string, error) {
538 return tp.EmailAddress, nil
541func (tp *TestProvider) ValidateSessionState(ctx context.Context, session *sessions.SessionState) bool {
542 return tp.ValidToken
545func TestBasicAuthPassword(t *testing.T) {
546 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
547 logger.Printf("%#v", r)
548 var payload string
549 switch r.URL.Path {
550 case "/oauth/token":
551 payload = `{"access_token": "my_auth_token"}`
552 default:
553 payload = r.Header.Get("Authorization")
554 if payload == "" {
555 payload = "No Authorization header found."
556 }
557 }
558 w.WriteHeader(200)
559 _, err := w.Write([]byte(payload))
560 if err != nil {
561 t.Fatal(err)
562 }
563 }))
564 opts := baseTestOptions()
565 opts.Upstreams = append(opts.Upstreams, providerServer.URL)
566 opts.Cookie.Secure = false
567 opts.PassBasicAuth = true
568 opts.SetBasicAuth = true
569 opts.PassUserHeaders = true
570 opts.PreferEmailToUser = true
571 opts.BasicAuthPassword = "This is a secure password"
572 err := validation.Validate(opts)
573 assert.NoError(t, err)
575 providerURL, _ := url.Parse(providerServer.URL)
576 const emailAddress = "john.doe@example.com"
578 opts.SetProvider(NewTestProvider(providerURL, emailAddress))
579 proxy, err := NewOAuthProxy(opts, func(email string) bool {
580 return email == emailAddress
581 })
582 if err != nil {
583 t.Fatal(err)
584 }
586 rw := httptest.NewRecorder()
587 req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:", strings.NewReader(""))
588 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
589 proxy.ServeHTTP(rw, req)
590 if rw.Code >= 400 {
591 t.Fatalf("expected 3xx got %d", rw.Code)
592 }
593 cookie := rw.Header().Values("Set-Cookie")[1]
595 cookieName := proxy.CookieName
596 var value string
597 keyPrefix := cookieName + "="
599 for _, field := range strings.Split(cookie, "; ") {
600 value = strings.TrimPrefix(field, keyPrefix)
601 if value != field {
602 break
603 } else {
604 value = ""
605 }
606 }
608 req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
609 req.AddCookie(&http.Cookie{
610 Name: cookieName,
611 Value: value,
612 Path: "/",
613 Expires: time.Now().Add(time.Duration(24)),
614 HttpOnly: true,
615 })
616 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
618 rw = httptest.NewRecorder()
619 proxy.ServeHTTP(rw, req)
621 // The username in the basic auth credentials is expected to be equal to the email address from the
622 // auth response, so we use the same variable here.
623 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
624 assert.Equal(t, expectedHeader, rw.Body.String())
625 providerServer.Close()
628func TestBasicAuthWithEmail(t *testing.T) {
629 opts := baseTestOptions()
630 opts.PassBasicAuth = true
631 opts.PassUserHeaders = false
632 opts.PreferEmailToUser = false
633 opts.BasicAuthPassword = "This is a secure password"
634 err := validation.Validate(opts)
635 assert.NoError(t, err)
637 const emailAddress = "john.doe@example.com"
638 const userName = "9fcab5c9b889a557"
640 // The username in the basic auth credentials is expected to be equal to the email address from the
641 expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
642 expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
644 created := time.Now()
645 session := &sessions.SessionState{
646 User: userName,
647 Email: emailAddress,
648 AccessToken: "oauth_token",
649 CreatedAt: &created,
650 }
651 {
652 rw := httptest.NewRecorder()
653 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
654 proxy, err := NewOAuthProxy(opts, func(email string) bool {
655 return email == emailAddress
656 })
657 if err != nil {
658 t.Fatal(err)
659 }
660 proxy.addHeadersForProxying(rw, req, session)
661 assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
662 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
663 }
665 opts.PreferEmailToUser = true
666 {
667 rw := httptest.NewRecorder()
668 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
670 proxy, err := NewOAuthProxy(opts, func(email string) bool {
671 return email == emailAddress
672 })
673 if err != nil {
674 t.Fatal(err)
675 }
676 proxy.addHeadersForProxying(rw, req, session)
677 assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
678 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
679 }
682func TestPassUserHeadersWithEmail(t *testing.T) {
683 opts := baseTestOptions()
684 err := validation.Validate(opts)
685 assert.NoError(t, err)
687 const emailAddress = "john.doe@example.com"
688 const userName = "9fcab5c9b889a557"
690 created := time.Now()
691 session := &sessions.SessionState{
692 User: userName,
693 Email: emailAddress,
694 AccessToken: "oauth_token",
695 CreatedAt: &created,
696 }
697 {
698 rw := httptest.NewRecorder()
699 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
700 proxy, err := NewOAuthProxy(opts, func(email string) bool {
701 return email == emailAddress
702 })
703 if err != nil {
704 t.Fatal(err)
705 }
706 proxy.addHeadersForProxying(rw, req, session)
707 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
708 }
710 opts.PreferEmailToUser = true
711 {
712 rw := httptest.NewRecorder()
713 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
715 proxy, err := NewOAuthProxy(opts, func(email string) bool {
716 return email == emailAddress
717 })
718 if err != nil {
719 t.Fatal(err)
720 }
721 proxy.addHeadersForProxying(rw, req, session)
722 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
723 }
726func TestStripAuthHeaders(t *testing.T) {
727 testCases := map[string]struct {
728 SkipAuthStripHeaders bool
729 PassBasicAuth bool
730 PassUserHeaders bool
731 PassAccessToken bool
732 PassAuthorization bool
733 StrippedHeaders map[string]bool
734 }{
735 "Default options": {
736 SkipAuthStripHeaders: true,
737 PassBasicAuth: true,
738 PassUserHeaders: true,
739 PassAccessToken: false,
740 PassAuthorization: false,
741 StrippedHeaders: map[string]bool{
742 "X-Forwarded-User": true,
743 "X-Forwarded-Email": true,
744 "X-Forwarded-Preferred-Username": true,
745 "X-Forwarded-Access-Token": false,
746 "Authorization": true,
747 },
748 },
749 "Pass access token": {
750 SkipAuthStripHeaders: true,
751 PassBasicAuth: true,
752 PassUserHeaders: true,
753 PassAccessToken: true,
754 PassAuthorization: false,
755 StrippedHeaders: map[string]bool{
756 "X-Forwarded-User": true,
757 "X-Forwarded-Email": true,
758 "X-Forwarded-Preferred-Username": true,
759 "X-Forwarded-Access-Token": true,
760 "Authorization": true,
761 },
762 },
763 "Nothing setting Authorization": {
764 SkipAuthStripHeaders: true,
765 PassBasicAuth: false,
766 PassUserHeaders: true,
767 PassAccessToken: true,
768 PassAuthorization: false,
769 StrippedHeaders: map[string]bool{
770 "X-Forwarded-User": true,
771 "X-Forwarded-Email": true,
772 "X-Forwarded-Preferred-Username": true,
773 "X-Forwarded-Access-Token": true,
774 "Authorization": false,
775 },
776 },
777 "Only Authorization header modified": {
778 SkipAuthStripHeaders: true,
779 PassBasicAuth: false,
780 PassUserHeaders: false,
781 PassAccessToken: false,
782 PassAuthorization: true,
783 StrippedHeaders: map[string]bool{
784 "X-Forwarded-User": false,
785 "X-Forwarded-Email": false,
786 "X-Forwarded-Preferred-Username": false,
787 "X-Forwarded-Access-Token": false,
788 "Authorization": true,
789 },
790 },
791 "Don't strip any headers (default options)": {
792 SkipAuthStripHeaders: false,
793 PassBasicAuth: true,
794 PassUserHeaders: true,
795 PassAccessToken: false,
796 PassAuthorization: false,
797 StrippedHeaders: map[string]bool{
798 "X-Forwarded-User": false,
799 "X-Forwarded-Email": false,
800 "X-Forwarded-Preferred-Username": false,
801 "X-Forwarded-Access-Token": false,
802 "Authorization": false,
803 },
804 },
805 "Don't strip any headers (custom options)": {
806 SkipAuthStripHeaders: false,
807 PassBasicAuth: true,
808 PassUserHeaders: true,
809 PassAccessToken: true,
810 PassAuthorization: false,
811 StrippedHeaders: map[string]bool{
812 "X-Forwarded-User": false,
813 "X-Forwarded-Email": false,
814 "X-Forwarded-Preferred-Username": false,
815 "X-Forwarded-Access-Token": false,
816 "Authorization": false,
817 },
818 },
819 }
821 initialHeaders := map[string]string{
822 "X-Forwarded-User": "9fcab5c9b889a557",
823 "X-Forwarded-Email": "john.doe@example.com",
824 "X-Forwarded-Preferred-Username": "john.doe",
825 "X-Forwarded-Access-Token": "AccessToken",
826 "Authorization": "bearer IDToken",
827 }
829 for name, tc := range testCases {
830 t.Run(name, func(t *testing.T) {
831 opts := baseTestOptions()
832 opts.SkipAuthStripHeaders = tc.SkipAuthStripHeaders
833 opts.PassBasicAuth = tc.PassBasicAuth
834 opts.PassUserHeaders = tc.PassUserHeaders
835 opts.PassAccessToken = tc.PassAccessToken
836 opts.PassAuthorization = tc.PassAuthorization
837 err := validation.Validate(opts)
838 assert.NoError(t, err)
840 req, _ := http.NewRequest("GET", fmt.Sprintf("%s/testCase", opts.ProxyPrefix), nil)
841 for header, val := range initialHeaders {
842 req.Header.Set(header, val)
843 }
845 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
846 assert.NoError(t, err)
847 if proxy.skipAuthStripHeaders {
848 proxy.stripAuthHeaders(req)
849 }
851 for header, stripped := range tc.StrippedHeaders {
852 if stripped {
853 assert.Equal(t, req.Header.Get(header), "")
854 } else {
855 assert.Equal(t, req.Header.Get(header), initialHeaders[header])
856 }
857 }
858 })
859 }
862type PassAccessTokenTest struct {
863 providerServer *httptest.Server
864 proxy *OAuthProxy
865 opts *options.Options
868type PassAccessTokenTestOptions struct {
869 PassAccessToken bool
870 ProxyUpstream string
873func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTest, error) {
874 patt := &PassAccessTokenTest{}
876 patt.providerServer = httptest.NewServer(
877 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
878 var payload string
879 switch r.URL.Path {
880 case "/oauth/token":
881 payload = `{"access_token": "my_auth_token"}`
882 default:
883 payload = r.Header.Get("X-Forwarded-Access-Token")
884 if payload == "" {
885 payload = "No access token found."
886 }
887 }
888 w.WriteHeader(200)
889 _, err := w.Write([]byte(payload))
890 if err != nil {
891 panic(err)
892 }
893 }))
895 patt.opts = baseTestOptions()
896 patt.opts.Upstreams = append(patt.opts.Upstreams, patt.providerServer.URL)
897 if opts.ProxyUpstream != "" {
898 patt.opts.Upstreams = append(patt.opts.Upstreams, opts.ProxyUpstream)
899 }
900 patt.opts.Cookie.Secure = false
901 patt.opts.PassAccessToken = opts.PassAccessToken
902 err := validation.Validate(patt.opts)
903 if err != nil {
904 return nil, err
905 }
907 providerURL, _ := url.Parse(patt.providerServer.URL)
908 const emailAddress = "michael.bland@gsa.gov"
910 patt.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
911 patt.proxy, err = NewOAuthProxy(patt.opts, func(email string) bool {
912 return email == emailAddress
913 })
914 if err != nil {
915 return nil, err
916 }
917 return patt, nil
920func (patTest *PassAccessTokenTest) Close() {
921 patTest.providerServer.Close()
924func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
925 cookie string) {
926 rw := httptest.NewRecorder()
927 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
928 strings.NewReader(""))
929 if err != nil {
930 return 0, ""
931 }
932 req.AddCookie(patTest.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
933 patTest.proxy.ServeHTTP(rw, req)
934 return rw.Code, rw.Header().Values("Set-Cookie")[1]
937// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
938// and cookie and returns body and status code.
939func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
940 cookieName := patTest.proxy.CookieName
941 var value string
942 keyPrefix := cookieName + "="
944 for _, field := range strings.Split(cookie, "; ") {
945 value = strings.TrimPrefix(field, keyPrefix)
946 if value != field {
947 break
948 } else {
949 value = ""
950 }
951 }
952 if value == "" {
953 return 0, ""
954 }
956 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
957 if err != nil {
958 return 0, ""
959 }
960 req.AddCookie(&http.Cookie{
961 Name: cookieName,
962 Value: value,
963 Path: "/",
964 Expires: time.Now().Add(time.Duration(24)),
965 HttpOnly: true,
966 })
968 rw := httptest.NewRecorder()
969 patTest.proxy.ServeHTTP(rw, req)
970 return rw.Code, rw.Body.String()
973func TestForwardAccessTokenUpstream(t *testing.T) {
974 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
975 PassAccessToken: true,
976 })
977 if err != nil {
978 t.Fatal(err)
979 }
980 t.Cleanup(patTest.Close)
982 // A successful validation will redirect and set the auth cookie.
983 code, cookie := patTest.getCallbackEndpoint()
984 if code != 302 {
985 t.Fatalf("expected 302; got %d", code)
986 }
987 assert.NotNil(t, cookie)
989 // Now we make a regular request; the access_token from the cookie is
990 // forwarded as the "X-Forwarded-Access-Token" header. The token is
991 // read by the test provider server and written in the response body.
992 code, payload := patTest.getEndpointWithCookie(cookie, "/")
993 if code != 200 {
994 t.Fatalf("expected 200; got %d", code)
995 }
996 assert.Equal(t, "my_auth_token", payload)
999func TestStaticProxyUpstream(t *testing.T) {
1000 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
1001 PassAccessToken: true,
1002 ProxyUpstream: "static://200/static-proxy",
1003 })
1004 if err != nil {
1005 t.Fatal(err)
1007 t.Cleanup(patTest.Close)
1009 // A successful validation will redirect and set the auth cookie.
1010 code, cookie := patTest.getCallbackEndpoint()
1011 if code != 302 {
1012 t.Fatalf("expected 302; got %d", code)
1014 assert.NotEqual(t, nil, cookie)
1016 // Now we make a regular request against the upstream proxy; And validate
1017 // the returned status code through the static proxy.
1018 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
1019 if code != 200 {
1020 t.Fatalf("expected 200; got %d", code)
1022 assert.Equal(t, "Authenticated", payload)
1025func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
1026 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
1027 PassAccessToken: false,
1028 })
1029 if err != nil {
1030 t.Fatal(err)
1032 t.Cleanup(patTest.Close)
1034 // A successful validation will redirect and set the auth cookie.
1035 code, cookie := patTest.getCallbackEndpoint()
1036 if code != 302 {
1037 t.Fatalf("expected 302; got %d", code)
1039 assert.NotEqual(t, nil, cookie)
1041 // Now we make a regular request, but the access token header should
1042 // not be present.
1043 code, payload := patTest.getEndpointWithCookie(cookie, "/")
1044 if code != 200 {
1045 t.Fatalf("expected 200; got %d", code)
1047 assert.Equal(t, "No access token found.", payload)
1050type SignInPageTest struct {
1051 opts *options.Options
1052 proxy *OAuthProxy
1053 signInRegexp *regexp.Regexp
1054 signInProviderRegexp *regexp.Regexp
1057const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
1058const signInSkipProvider = `>Found<`
1060func NewSignInPageTest(skipProvider bool) (*SignInPageTest, error) {
1061 var sipTest SignInPageTest
1063 sipTest.opts = baseTestOptions()
1064 sipTest.opts.SkipProviderButton = skipProvider
1065 err := validation.Validate(sipTest.opts)
1066 if err != nil {
1067 return nil, err
1070 sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
1071 return true
1072 })
1073 if err != nil {
1074 return nil, err
1076 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
1077 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
1079 return &sipTest, nil
1082func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
1083 rw := httptest.NewRecorder()
1084 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
1085 sipTest.proxy.ServeHTTP(rw, req)
1086 return rw.Code, rw.Body.String()
1089func TestSignInPageIncludesTargetRedirect(t *testing.T) {
1090 sipTest, err := NewSignInPageTest(false)
1091 if err != nil {
1092 t.Fatal(err)
1094 const endpoint = "/some/random/endpoint"
1096 code, body := sipTest.GetEndpoint(endpoint)
1097 assert.Equal(t, 403, code)
1099 match := sipTest.signInRegexp.FindStringSubmatch(body)
1100 if match == nil {
1101 t.Fatal("Did not find pattern in body: " +
1102 signInRedirectPattern + "\nBody:\n" + body)
1104 if match[1] != endpoint {
1105 t.Fatal(`expected redirect to "` + endpoint +
1106 `", but was "` + match[1] + `"`)
1110func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
1111 sipTest, err := NewSignInPageTest(false)
1112 if err != nil {
1113 t.Fatal(err)
1115 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
1116 assert.Equal(t, 200, code)
1118 match := sipTest.signInRegexp.FindStringSubmatch(body)
1119 if match == nil {
1120 t.Fatal("Did not find pattern in body: " +
1121 signInRedirectPattern + "\nBody:\n" + body)
1123 if match[1] != "/" {
1124 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
1128func TestSignInPageSkipProvider(t *testing.T) {
1129 sipTest, err := NewSignInPageTest(true)
1130 if err != nil {
1131 t.Fatal(err)
1134 endpoint := "/some/random/endpoint"
1136 code, body := sipTest.GetEndpoint(endpoint)
1137 assert.Equal(t, 302, code)
1139 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
1140 if match == nil {
1141 t.Fatal("Did not find pattern in body: " +
1142 signInSkipProvider + "\nBody:\n" + body)
1146func TestSignInPageSkipProviderDirect(t *testing.T) {
1147 sipTest, err := NewSignInPageTest(true)
1148 if err != nil {
1149 t.Fatal(err)
1152 endpoint := "/sign_in"
1154 code, body := sipTest.GetEndpoint(endpoint)
1155 assert.Equal(t, 302, code)
1157 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
1158 if match == nil {
1159 t.Fatal("Did not find pattern in body: " +
1160 signInSkipProvider + "\nBody:\n" + body)
1164type ProcessCookieTest struct {
1165 opts *options.Options
1166 proxy *OAuthProxy
1167 rw *httptest.ResponseRecorder
1168 req *http.Request
1169 validateUser bool
1172type ProcessCookieTestOpts struct {
1173 providerValidateCookieResponse bool
1176type OptionsModifier func(*options.Options)
1178func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1179 var pcTest ProcessCookieTest
1181 pcTest.opts = baseTestOptions()
1182 for _, modifier := range modifiers {
1183 modifier(pcTest.opts)
1185 // First, set the CookieRefresh option so proxy.AesCipher is created,
1186 // needed to encrypt the access_token.
1187 pcTest.opts.Cookie.Refresh = time.Hour
1188 err := validation.Validate(pcTest.opts)
1189 if err != nil {
1190 return nil, err
1193 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1194 return pcTest.validateUser
1195 })
1196 if err != nil {
1197 return nil, err
1199 pcTest.proxy.provider = &TestProvider{
1200 ValidToken: opts.providerValidateCookieResponse,
1203 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
1204 // access_token validation.
1205 pcTest.proxy.CookieRefresh = time.Duration(0)
1206 pcTest.rw = httptest.NewRecorder()
1207 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
1208 pcTest.validateUser = true
1209 return &pcTest, nil
1212func NewProcessCookieTestWithDefaults() (*ProcessCookieTest, error) {
1213 return NewProcessCookieTest(ProcessCookieTestOpts{
1214 providerValidateCookieResponse: true,
1215 })
1218func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1219 return NewProcessCookieTest(ProcessCookieTestOpts{
1220 providerValidateCookieResponse: true,
1221 }, modifiers...)
1224func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
1225 err := p.proxy.SaveSession(p.rw, p.req, s)
1226 if err != nil {
1227 return err
1229 for _, cookie := range p.rw.Result().Cookies() {
1230 p.req.AddCookie(cookie)
1232 return nil
1235func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
1236 return p.proxy.LoadCookiedSession(p.req)
1239func TestLoadCookiedSession(t *testing.T) {
1240 pcTest, err := NewProcessCookieTestWithDefaults()
1241 if err != nil {
1242 t.Fatal(err)
1245 created := time.Now()
1246 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
1247 err = pcTest.SaveSession(startSession)
1248 assert.NoError(t, err)
1250 session, err := pcTest.LoadCookiedSession()
1251 if err != nil {
1252 t.Fatal(err)
1254 assert.Equal(t, startSession.Email, session.Email)
1255 assert.Equal(t, "", session.User)
1256 assert.Equal(t, startSession.AccessToken, session.AccessToken)
1259func TestProcessCookieNoCookieError(t *testing.T) {
1260 pcTest, err := NewProcessCookieTestWithDefaults()
1261 if err != nil {
1262 t.Fatal(err)
1265 session, err := pcTest.LoadCookiedSession()
1266 assert.Error(t, err, "cookie \"_oauth2_proxy\" not present")
1267 if session != nil {
1268 t.Errorf("expected nil session. got %#v", session)
1272func TestProcessCookieRefreshNotSet(t *testing.T) {
1273 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1274 opts.Cookie.Expire = time.Duration(23) * time.Hour
1275 })
1276 if err != nil {
1277 t.Fatal(err)
1280 reference := time.Now().Add(time.Duration(-2) * time.Hour)
1282 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1283 err = pcTest.SaveSession(startSession)
1284 assert.NoError(t, err)
1286 session, err := pcTest.LoadCookiedSession()
1287 assert.Equal(t, nil, err)
1288 if session.Age() < time.Duration(-2)*time.Hour {
1289 t.Errorf("cookie too young %v", session.Age())
1291 assert.Equal(t, startSession.Email, session.Email)
1294func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1295 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1296 opts.Cookie.Expire = time.Duration(24) * time.Hour
1297 })
1298 if err != nil {
1299 t.Fatal(err)
1302 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1303 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1304 err = pcTest.SaveSession(startSession)
1305 assert.NoError(t, err)
1307 session, err := pcTest.LoadCookiedSession()
1308 assert.NotEqual(t, nil, err)
1309 if session != nil {
1310 t.Errorf("expected nil session %#v", session)
1314func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1315 pcTest, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1316 opts.Cookie.Expire = time.Duration(24) * time.Hour
1317 })
1318 if err != nil {
1319 t.Fatal(err)
1322 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1323 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1324 err = pcTest.SaveSession(startSession)
1325 assert.NoError(t, err)
1327 pcTest.proxy.CookieRefresh = time.Hour
1328 session, err := pcTest.LoadCookiedSession()
1329 assert.NotEqual(t, nil, err)
1330 if session != nil {
1331 t.Errorf("expected nil session %#v", session)
1335func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
1336 pcTest, err := NewProcessCookieTestWithDefaults()
1337 if err != nil {
1338 return nil, err
1340 pcTest.req, _ = http.NewRequest("GET",
1341 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1342 return pcTest, nil
1345func TestUserInfoEndpointAccepted(t *testing.T) {
1346 test, err := NewUserInfoEndpointTest()
1347 if err != nil {
1348 t.Fatal(err)
1351 startSession := &sessions.SessionState{
1352 Email: "john.doe@example.com", AccessToken: "my_access_token"}
1353 err = test.SaveSession(startSession)
1354 assert.NoError(t, err)
1356 test.proxy.ServeHTTP(test.rw, test.req)
1357 assert.Equal(t, http.StatusOK, test.rw.Code)
1358 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1359 assert.Equal(t, "{\"email\":\"john.doe@example.com\"}\n", string(bodyBytes))
1362func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1363 test, err := NewUserInfoEndpointTest()
1364 if err != nil {
1365 t.Fatal(err)
1368 test.proxy.ServeHTTP(test.rw, test.req)
1369 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1372func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) (*ProcessCookieTest, error) {
1373 pcTest, err := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1374 if err != nil {
1375 return nil, err
1377 pcTest.req, _ = http.NewRequest("GET",
1378 pcTest.opts.ProxyPrefix+"/auth", nil)
1379 return pcTest, nil
1382func TestAuthOnlyEndpointAccepted(t *testing.T) {
1383 test, err := NewAuthOnlyEndpointTest()
1384 if err != nil {
1385 t.Fatal(err)
1388 created := time.Now()
1389 startSession := &sessions.SessionState{
1390 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1391 err = test.SaveSession(startSession)
1392 assert.NoError(t, err)
1394 test.proxy.ServeHTTP(test.rw, test.req)
1395 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1396 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1397 assert.Equal(t, "", string(bodyBytes))
1400func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1401 test, err := NewAuthOnlyEndpointTest()
1402 if err != nil {
1403 t.Fatal(err)
1406 test.proxy.ServeHTTP(test.rw, test.req)
1407 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1408 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1409 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1412func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1413 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1414 opts.Cookie.Expire = time.Duration(24) * time.Hour
1415 })
1416 if err != nil {
1417 t.Fatal(err)
1420 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1421 startSession := &sessions.SessionState{
1422 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1423 err = test.SaveSession(startSession)
1424 assert.NoError(t, err)
1426 test.proxy.ServeHTTP(test.rw, test.req)
1427 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1428 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1429 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1432func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1433 test, err := NewAuthOnlyEndpointTest()
1434 if err != nil {
1435 t.Fatal(err)
1438 created := time.Now()
1439 startSession := &sessions.SessionState{
1440 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1441 err = test.SaveSession(startSession)
1442 assert.NoError(t, err)
1443 test.validateUser = false
1445 test.proxy.ServeHTTP(test.rw, test.req)
1446 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1447 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1448 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1451func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1452 var pcTest ProcessCookieTest
1454 pcTest.opts = baseTestOptions()
1455 pcTest.opts.SetXAuthRequest = true
1456 err := validation.Validate(pcTest.opts)
1457 assert.NoError(t, err)
1459 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1460 return pcTest.validateUser
1461 })
1462 if err != nil {
1463 t.Fatal(err)
1465 pcTest.proxy.provider = &TestProvider{
1466 ValidToken: true,
1469 pcTest.validateUser = true
1471 pcTest.rw = httptest.NewRecorder()
1472 pcTest.req, _ = http.NewRequest("GET",
1473 pcTest.opts.ProxyPrefix+"/auth", nil)
1475 created := time.Now()
1476 startSession := &sessions.SessionState{
1477 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1478 err = pcTest.SaveSession(startSession)
1479 assert.NoError(t, err)
1481 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1482 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1483 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1484 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1487func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1488 var pcTest ProcessCookieTest
1490 pcTest.opts = baseTestOptions()
1491 pcTest.opts.SetXAuthRequest = true
1492 pcTest.opts.SetBasicAuth = true
1493 err := validation.Validate(pcTest.opts)
1494 assert.NoError(t, err)
1496 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1497 return pcTest.validateUser
1498 })
1499 if err != nil {
1500 t.Fatal(err)
1502 pcTest.proxy.provider = &TestProvider{
1503 ValidToken: true,
1506 pcTest.validateUser = true
1508 pcTest.rw = httptest.NewRecorder()
1509 pcTest.req, _ = http.NewRequest("GET",
1510 pcTest.opts.ProxyPrefix+"/auth", nil)
1512 created := time.Now()
1513 startSession := &sessions.SessionState{
1514 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1515 err = pcTest.SaveSession(startSession)
1516 assert.NoError(t, err)
1518 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1519 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1520 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1521 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1522 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:"+pcTest.opts.BasicAuthPassword))
1523 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1526func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1527 var pcTest ProcessCookieTest
1529 pcTest.opts = baseTestOptions()
1530 pcTest.opts.SetXAuthRequest = true
1531 pcTest.opts.SetBasicAuth = false
1532 err := validation.Validate(pcTest.opts)
1533 assert.NoError(t, err)
1535 pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
1536 return pcTest.validateUser
1537 })
1538 if err != nil {
1539 t.Fatal(err)
1541 pcTest.proxy.provider = &TestProvider{
1542 ValidToken: true,
1545 pcTest.validateUser = true
1547 pcTest.rw = httptest.NewRecorder()
1548 pcTest.req, _ = http.NewRequest("GET",
1549 pcTest.opts.ProxyPrefix+"/auth", nil)
1551 created := time.Now()
1552 startSession := &sessions.SessionState{
1553 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1554 err = pcTest.SaveSession(startSession)
1555 assert.NoError(t, err)
1557 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1558 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1559 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1560 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1561 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1564func TestAuthSkippedForPreflightRequests(t *testing.T) {
1565 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1566 w.WriteHeader(200)
1567 _, err := w.Write([]byte("response"))
1568 if err != nil {
1569 t.Fatal(err)
1571 }))
1572 t.Cleanup(upstream.Close)
1574 opts := baseTestOptions()
1575 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1576 opts.SkipAuthPreflight = true
1577 err := validation.Validate(opts)
1578 assert.NoError(t, err)
1580 upstreamURL, _ := url.Parse(upstream.URL)
1581 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1583 proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
1584 if err != nil {
1585 t.Fatal(err)
1587 rw := httptest.NewRecorder()
1588 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1589 proxy.ServeHTTP(rw, req)
1591 assert.Equal(t, 200, rw.Code)
1592 assert.Equal(t, "response", rw.Body.String())
1595type SignatureAuthenticator struct {
1596 auth hmacauth.HmacAuth
1599func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1600 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1602 var msg string
1603 switch result {
1604 case hmacauth.ResultNoSignature:
1605 msg = "no signature received"
1606 case hmacauth.ResultMatch:
1607 msg = "signatures match"
1608 case hmacauth.ResultMismatch:
1609 msg = fmt.Sprintf(
1610 "signatures do not match:\n received: %s\n computed: %s",
1611 headerSig,
1612 computedSig)
1613 default:
1614 panic("unknown result value: " + result.String())
1617 _, err := w.Write([]byte(msg))
1618 if err != nil {
1619 panic(err)
1623type SignatureTest struct {
1624 opts *options.Options
1625 upstream *httptest.Server
1626 upstreamHost string
1627 provider *httptest.Server
1628 header http.Header
1629 rw *httptest.ResponseRecorder
1630 authenticator *SignatureAuthenticator
1633func NewSignatureTest() (*SignatureTest, error) {
1634 opts := baseTestOptions()
1635 opts.EmailDomains = []string{"acm.org"}
1637 authenticator := &SignatureAuthenticator{}
1638 upstream := httptest.NewServer(
1639 http.HandlerFunc(authenticator.Authenticate))
1640 upstreamURL, err := url.Parse(upstream.URL)
1641 if err != nil {
1642 return nil, err
1644 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1646 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1647 _, err := w.Write([]byte(`{"access_token": "my_auth_token"}`))
1648 if err != nil {
1649 panic(err)
1652 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1653 providerURL, err := url.Parse(provider.URL)
1654 if err != nil {
1655 return nil, err
1657 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1659 return &SignatureTest{
1660 opts,
1661 upstream,
1662 upstreamURL.Host,
1663 provider,
1664 make(http.Header),
1665 httptest.NewRecorder(),
1666 authenticator,
1667 }, nil
1670func (st *SignatureTest) Close() {
1671 st.provider.Close()
1672 st.upstream.Close()
1675// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1676// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1677// https://github.com/18F/hmacauth/pull/4
1678type fakeNetConn struct {
1679 reqBody string
1682func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1683 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1684 copy(p, fnc.reqBody)
1685 fnc.reqBody = ""
1686 return bodyLen, io.EOF
1688 return 0, io.EOF
1691func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) error {
1692 err := validation.Validate(st.opts)
1693 if err != nil {
1694 return err
1696 proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
1697 if err != nil {
1698 return err
1701 var bodyBuf io.ReadCloser
1702 if body != "" {
1703 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1705 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1706 req.Header = st.header
1708 state := &sessions.SessionState{
1709 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1710 err = proxy.SaveSession(st.rw, req, state)
1711 if err != nil {
1712 return err
1714 for _, c := range st.rw.Result().Cookies() {
1715 req.AddCookie(c)
1717 // This is used by the upstream to validate the signature.
1718 st.authenticator.auth = hmacauth.NewHmacAuth(
1719 crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
1720 proxy.ServeHTTP(st.rw, req)
1722 return nil
1725func TestRequestSignature(t *testing.T) {
1726 testCases := map[string]struct {
1727 method string
1728 body string
1729 key string
1730 resp string
1731 }{
1732 "No request signature": {
1733 method: "GET",
1734 body: "",
1735 key: "",
1736 resp: "no signature received",
1737 },
1738 "Get request": {
1739 method: "GET",
1740 body: "",
1741 key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1742 resp: "signatures match",
1743 },
1744 "Post request": {
1745 method: "POST",
1746 body: `{ "hello": "world!" }`,
1747 key: "d90df39e2d19282840252612dd7c81421a372f61",
1748 resp: "signatures match",
1749 },
1751 for name, tc := range testCases {
1752 t.Run(name, func(t *testing.T) {
1753 st, err := NewSignatureTest()
1754 if err != nil {
1755 t.Fatal(err)
1757 t.Cleanup(st.Close)
1758 if tc.key != "" {
1759 st.opts.SignatureKey = fmt.Sprintf("sha1:%s", tc.key)
1761 err = st.MakeRequestWithExpectedKey(tc.method, tc.body, tc.key)
1762 assert.NoError(t, err)
1763 assert.Equal(t, 200, st.rw.Code)
1764 assert.Equal(t, tc.resp, st.rw.Body.String())
1765 })
1769func TestGetRedirect(t *testing.T) {
1770 opts := baseTestOptions()
1771 err := validation.Validate(opts)
1772 assert.NoError(t, err)
1773 require.NotEmpty(t, opts.ProxyPrefix)
1774 proxy, err := NewOAuthProxy(opts, func(s string) bool { return false })
1775 if err != nil {
1776 t.Fatal(err)
1779 tests := []struct {
1780 name string
1781 url string
1782 expectedRedirect string
1783 }{
1785 name: "request outside of ProxyPrefix redirects to original URL",
1786 url: "/foo/bar",
1787 expectedRedirect: "/foo/bar",
1788 },
1790 name: "request under ProxyPrefix redirects to root",
1791 url: proxy.ProxyPrefix + "/foo/bar",
1792 expectedRedirect: "/",
1793 },
1795 for _, tt := range tests {
1796 t.Run(tt.name, func(t *testing.T) {
1797 req, _ := http.NewRequest("GET", tt.url, nil)
1798 redirect, err := proxy.GetRedirect(req)
1800 assert.NoError(t, err)
1801 assert.Equal(t, tt.expectedRedirect, redirect)
1802 })
1806type ajaxRequestTest struct {
1807 opts *options.Options
1808 proxy *OAuthProxy
1811func newAjaxRequestTest() (*ajaxRequestTest, error) {
1812 test := &ajaxRequestTest{}
1813 test.opts = baseTestOptions()
1814 err := validation.Validate(test.opts)
1815 if err != nil {
1816 return nil, err
1819 test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
1820 return true
1821 })
1822 if err != nil {
1823 return nil, err
1825 return test, nil
1828func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1829 rw := httptest.NewRecorder()
1830 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1831 if err != nil {
1832 return 0, nil, err
1834 req.Header = header
1835 test.proxy.ServeHTTP(rw, req)
1836 return rw.Code, rw.Header(), nil
1839func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1840 test, err := newAjaxRequestTest()
1841 if err != nil {
1842 t.Fatal(err)
1844 endpoint := "/test"
1846 code, rh, err := test.getEndpoint(endpoint, header)
1847 assert.NoError(t, err)
1848 assert.Equal(t, http.StatusUnauthorized, code)
1849 mime := rh.Get("Content-Type")
1850 assert.Equal(t, applicationJSON, mime)
1852func TestAjaxUnauthorizedRequest1(t *testing.T) {
1853 header := make(http.Header)
1854 header.Add("accept", applicationJSON)
1856 testAjaxUnauthorizedRequest(t, header)
1859func TestAjaxUnauthorizedRequest2(t *testing.T) {
1860 header := make(http.Header)
1861 header.Add("Accept", applicationJSON)
1863 testAjaxUnauthorizedRequest(t, header)
1866func TestAjaxForbiddendRequest(t *testing.T) {
1867 test, err := newAjaxRequestTest()
1868 if err != nil {
1869 t.Fatal(err)
1871 endpoint := "/test"
1872 header := make(http.Header)
1873 code, rh, err := test.getEndpoint(endpoint, header)
1874 assert.NoError(t, err)
1875 assert.Equal(t, http.StatusForbidden, code)
1876 mime := rh.Get("Content-Type")
1877 assert.NotEqual(t, applicationJSON, mime)
1880func TestClearSplitCookie(t *testing.T) {
1881 opts := baseTestOptions()
1882 opts.Cookie.Secret = base64CookieSecret
1883 opts.Cookie.Name = "oauth2"
1884 opts.Cookie.Domains = []string{"abc"}
1885 err := validation.Validate(opts)
1886 assert.NoError(t, err)
1888 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1889 if err != nil {
1890 t.Fatal(err)
1893 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1894 var rw = httptest.NewRecorder()
1895 req := httptest.NewRequest("get", "/", nil)
1897 req.AddCookie(&http.Cookie{
1898 Name: "test1",
1899 Value: "test1",
1900 })
1901 req.AddCookie(&http.Cookie{
1902 Name: "oauth2_0",
1903 Value: "oauth2_0",
1904 })
1905 req.AddCookie(&http.Cookie{
1906 Name: "oauth2_1",
1907 Value: "oauth2_1",
1908 })
1910 err = p.ClearSessionCookie(rw, req)
1911 assert.NoError(t, err)
1912 header := rw.Header()
1914 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1917func TestClearSingleCookie(t *testing.T) {
1918 opts := baseTestOptions()
1919 opts.Cookie.Name = "oauth2"
1920 opts.Cookie.Domains = []string{"abc"}
1921 store, err := sessionscookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1922 if err != nil {
1923 t.Fatal(err)
1926 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1927 var rw = httptest.NewRecorder()
1928 req := httptest.NewRequest("get", "/", nil)
1930 req.AddCookie(&http.Cookie{
1931 Name: "test1",
1932 Value: "test1",
1933 })
1934 req.AddCookie(&http.Cookie{
1935 Name: "oauth2",
1936 Value: "oauth2",
1937 })
1939 err = p.ClearSessionCookie(rw, req)
1940 assert.NoError(t, err)
1941 header := rw.Header()
1943 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1946type NoOpKeySet struct {
1949func (NoOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
1950 splitStrings := strings.Split(jwt, ".")
1951 payloadString := splitStrings[1]
1952 return base64.RawURLEncoding.DecodeString(payloadString)
1955func TestGetJwtSession(t *testing.T) {
1956 /* token payload:
1958 "sub": "1234567890",
1959 "aud": "https://test.myapp.com",
1960 "name": "John Doe",
1961 "email": "john@example.com",
1962 "iss": "https://issuer.example.com",
1963 "iat": 1553691215,
1964 "exp": 1912151821
1966 */
1967 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1968 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1969 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1970 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1971 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1972 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1974 keyset := NoOpKeySet{}
1975 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1976 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1978 test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1979 opts.PassAuthorization = true
1980 opts.SetAuthorization = true
1981 opts.SetXAuthRequest = true
1982 opts.SkipJwtBearerTokens = true
1983 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
1984 })
1985 if err != nil {
1986 t.Fatal(err)
1988 tp, _ := test.proxy.provider.(*TestProvider)
1989 tp.GroupValidator = func(s string) bool {
1990 return true
1993 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1994 test.req.Header = map[string][]string{
1995 "Authorization": {authHeader},
1998 // Bearer
1999 expires := time.Unix(1912151821, 0)
2000 session, err := test.proxy.GetJwtSession(test.req)
2001 assert.NoError(t, err)
2002 assert.Equal(t, session.User, "1234567890")
2003 assert.Equal(t, session.Email, "john@example.com")
2004 assert.Equal(t, session.ExpiresOn, &expires)
2005 assert.Equal(t, session.IDToken, goodJwt)
2007 test.proxy.ServeHTTP(test.rw, test.req)
2008 if test.rw.Code >= 400 {
2009 t.Fatalf("expected 3xx got %d", test.rw.Code)
2012 // Check PassAuthorization, should overwrite Basic header
2013 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
2014 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "1234567890")
2015 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
2017 // SetAuthorization and SetXAuthRequest
2018 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
2019 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "1234567890")
2020 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
2023func TestFindJwtBearerToken(t *testing.T) {
2024 p := OAuthProxy{CookieName: "oauth2", CookieDomains: []string{"abc"}}
2025 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}}
2027 validToken := "eyJfoobar.eyJfoobar.12345asdf"
2028 var token string
2030 // Bearer
2031 getReq.Header = map[string][]string{
2032 "Authorization": {fmt.Sprintf("Bearer %s", validToken)},
2035 token, err := p.findBearerToken(getReq)
2036 assert.NoError(t, err)
2037 assert.Equal(t, validToken, token)
2039 // Basic - no password
2040 getReq.SetBasicAuth(token, "")
2041 token, err = p.findBearerToken(getReq)
2042 assert.NoError(t, err)
2043 assert.Equal(t, validToken, token)
2045 // Basic - sentinel password
2046 getReq.SetBasicAuth(token, "x-oauth-basic")
2047 token, err = p.findBearerToken(getReq)
2048 assert.NoError(t, err)
2049 assert.Equal(t, validToken, token)
2051 // Basic - any username, password matching jwt pattern
2052 getReq.SetBasicAuth("any-username-you-could-wish-for", token)
2053 token, err = p.findBearerToken(getReq)
2054 assert.NoError(t, err)
2055 assert.Equal(t, validToken, token)
2057 failures := []string{
2058 // Too many parts
2059 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
2060 // Not enough parts
2061 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA",
2062 // Invalid encrypted key
2063 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.//////.dGVzdA.dGVzdA.dGVzdA",
2064 // Invalid IV
2065 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.//////.dGVzdA.dGVzdA",
2066 // Invalid ciphertext
2067 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.//////.dGVzdA",
2068 // Invalid tag
2069 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.//////",
2070 // Invalid header
2071 "W10.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
2072 // Invalid header
2073 "######.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
2074 // Missing alc/enc params
2075 "e30.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
2078 for _, failure := range failures {
2079 getReq.Header = map[string][]string{
2080 "Authorization": {fmt.Sprintf("Bearer %s", failure)},
2082 _, err := p.findBearerToken(getReq)
2083 assert.Error(t, err)
2087func Test_prepareNoCache(t *testing.T) {
2088 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2089 prepareNoCache(w)
2090 })
2091 mux := http.NewServeMux()
2092 mux.Handle("/", handler)
2094 rec := httptest.NewRecorder()
2095 req := httptest.NewRequest(http.MethodGet, "/", nil)
2096 mux.ServeHTTP(rec, req)
2098 for k, v := range noCacheHeaders {
2099 assert.Equal(t, rec.Header().Get(k), v)
2103func Test_noCacheHeaders(t *testing.T) {
2104 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2105 _, err := w.Write([]byte("upstream"))
2106 if err != nil {
2107 t.Error(err)
2109 }))
2110 t.Cleanup(upstream.Close)
2112 opts := baseTestOptions()
2113 opts.Upstreams = []string{upstream.URL}
2114 opts.SkipAuthRegex = []string{".*"}
2115 err := validation.Validate(opts)
2116 assert.NoError(t, err)
2117 proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2118 if err != nil {
2119 t.Fatal(err)
2122 t.Run("not exist in response from upstream", func(t *testing.T) {
2123 rec := httptest.NewRecorder()
2124 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
2125 proxy.ServeHTTP(rec, req)
2127 assert.Equal(t, http.StatusOK, rec.Code)
2128 assert.Equal(t, "upstream", rec.Body.String())
2130 // checking noCacheHeaders does not exists in response headers from upstream
2131 for k := range noCacheHeaders {
2132 assert.Equal(t, "", rec.Header().Get(k))
2134 })
2136 t.Run("has no-cache", func(t *testing.T) {
2137 tests := []struct {
2138 path string
2139 hasNoCache bool
2140 }{
2142 path: "/oauth2/sign_in",
2143 hasNoCache: true,
2144 },
2146 path: "/oauth2/sign_out",
2147 hasNoCache: true,
2148 },
2150 path: "/oauth2/start",
2151 hasNoCache: true,
2152 },
2154 path: "/oauth2/callback",
2155 hasNoCache: true,
2156 },
2158 path: "/oauth2/auth",
2159 hasNoCache: false,
2160 },
2162 path: "/oauth2/userinfo",
2163 hasNoCache: true,
2164 },
2166 path: "/upstream",
2167 hasNoCache: false,
2168 },
2171 for _, tt := range tests {
2172 t.Run(tt.path, func(t *testing.T) {
2173 rec := httptest.NewRecorder()
2174 req := httptest.NewRequest(http.MethodGet, tt.path, nil)
2175 proxy.ServeHTTP(rec, req)
2176 cacheControl := rec.Result().Header.Get("Cache-Control")
2177 if tt.hasNoCache != (strings.Contains(cacheControl, "no-cache")) {
2178 t.Errorf(`unexpected "Cache-Control" header: %s`, cacheControl)
2180 })
2183 })
2186func baseTestOptions() *options.Options {
2187 opts := options.NewOptions()
2188 opts.Cookie.Secret = rawCookieSecret
2189 opts.ClientID = clientID
2190 opts.ClientSecret = clientSecret
2191 opts.EmailDomains = []string{"*"}
2192 return opts
2195func TestTrustedIPs(t *testing.T) {
2196 tests := []struct {
2197 name string
2198 trustedIPs []string
2199 reverseProxy bool
2200 realClientIPHeader string
2201 req *http.Request
2202 expectTrusted bool
2203 }{
2204 // Check unconfigured behavior.
2206 name: "Default",
2207 trustedIPs: nil,
2208 reverseProxy: false,
2209 realClientIPHeader: "X-Real-IP", // Default value
2210 req: func() *http.Request {
2211 req, _ := http.NewRequest("GET", "/", nil)
2212 return req
2213 }(),
2214 expectTrusted: false,
2215 },
2216 // Check using req.RemoteAddr (Options.ReverseProxy == false).
2218 name: "WithRemoteAddr",
2219 trustedIPs: []string{"127.0.0.1"},
2220 reverseProxy: false,
2221 realClientIPHeader: "X-Real-IP", // Default value
2222 req: func() *http.Request {
2223 req, _ := http.NewRequest("GET", "/", nil)
2224 req.RemoteAddr = "127.0.0.1:43670"
2225 return req
2226 }(),
2227 expectTrusted: true,
2228 },
2229 // Check ignores req.RemoteAddr match when behind a reverse proxy / missing header.
2231 name: "IgnoresRemoteAddrInReverseProxyMode",
2232 trustedIPs: []string{"127.0.0.1"},
2233 reverseProxy: true,
2234 realClientIPHeader: "X-Real-IP", // Default value
2235 req: func() *http.Request {
2236 req, _ := http.NewRequest("GET", "/", nil)
2237 req.RemoteAddr = "127.0.0.1:44324"
2238 return req
2239 }(),
2240 expectTrusted: false,
2241 },
2242 // Check successful trusting of localhost in IPv4.
2244 name: "TrustsLocalhostInReverseProxyMode",
2245 trustedIPs: []string{"127.0.0.0/8", "::1"},
2246 reverseProxy: true,
2247 realClientIPHeader: "X-Forwarded-For",
2248 req: func() *http.Request {
2249 req, _ := http.NewRequest("GET", "/", nil)
2250 req.Header.Add("X-Forwarded-For", "127.0.0.1")
2251 return req
2252 }(),
2253 expectTrusted: true,
2254 },
2255 // Check successful trusting of localhost in IPv6.
2257 name: "TrustsIP6LocalostInReverseProxyMode",
2258 trustedIPs: []string{"127.0.0.0/8", "::1"},
2259 reverseProxy: true,
2260 realClientIPHeader: "X-Forwarded-For",
2261 req: func() *http.Request {
2262 req, _ := http.NewRequest("GET", "/", nil)
2263 req.Header.Add("X-Forwarded-For", "::1")
2264 return req
2265 }(),
2266 expectTrusted: true,
2267 },
2268 // Check does not trust random IPv4 address.
2270 name: "DoesNotTrustRandomIP4Address",
2271 trustedIPs: []string{"127.0.0.0/8", "::1"},
2272 reverseProxy: true,
2273 realClientIPHeader: "X-Forwarded-For",
2274 req: func() *http.Request {
2275 req, _ := http.NewRequest("GET", "/", nil)
2276 req.Header.Add("X-Forwarded-For", "12.34.56.78")
2277 return req
2278 }(),
2279 expectTrusted: false,
2280 },
2281 // Check does not trust random IPv6 address.
2283 name: "DoesNotTrustRandomIP6Address",
2284 trustedIPs: []string{"127.0.0.0/8", "::1"},
2285 reverseProxy: true,
2286 realClientIPHeader: "X-Forwarded-For",
2287 req: func() *http.Request {
2288 req, _ := http.NewRequest("GET", "/", nil)
2289 req.Header.Add("X-Forwarded-For", "::2")
2290 return req
2291 }(),
2292 expectTrusted: false,
2293 },
2294 // Check respects correct header.
2296 name: "RespectsCorrectHeaderInReverseProxyMode",
2297 trustedIPs: []string{"127.0.0.0/8", "::1"},
2298 reverseProxy: true,
2299 realClientIPHeader: "X-Forwarded-For",
2300 req: func() *http.Request {
2301 req, _ := http.NewRequest("GET", "/", nil)
2302 req.Header.Add("X-Real-IP", "::1")
2303 return req
2304 }(),
2305 expectTrusted: false,
2306 },
2307 // Check doesn't trust if garbage is provided.
2309 name: "DoesNotTrustGarbageInReverseProxyMode",
2310 trustedIPs: []string{"127.0.0.0/8", "::1"},
2311 reverseProxy: true,
2312 realClientIPHeader: "X-Forwarded-For",
2313 req: func() *http.Request {
2314 req, _ := http.NewRequest("GET", "/", nil)
2315 req.Header.Add("X-Forwarded-For", "adsfljk29242as!!")
2316 return req
2317 }(),
2318 expectTrusted: false,
2319 },
2320 // Check doesn't trust if garbage is provided (no reverse-proxy).
2322 name: "DoesNotTrustGarbage",
2323 trustedIPs: []string{"127.0.0.0/8", "::1"},
2324 reverseProxy: false,
2325 realClientIPHeader: "X-Real-IP",
2326 req: func() *http.Request {
2327 req, _ := http.NewRequest("GET", "/", nil)
2328 req.RemoteAddr = "adsfljk29242as!!"
2329 return req
2330 }(),
2331 expectTrusted: false,
2332 },
2335 for _, tt := range tests {
2336 t.Run(tt.name, func(t *testing.T) {
2337 opts := baseTestOptions()
2338 opts.Upstreams = []string{"static://200"}
2339 opts.TrustedIPs = tt.trustedIPs
2340 opts.ReverseProxy = tt.reverseProxy
2341 opts.RealClientIPHeader = tt.realClientIPHeader
2342 validation.Validate(opts)
2344 proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
2345 assert.NoError(t, err)
2346 rw := httptest.NewRecorder()
2348 proxy.ServeHTTP(rw, tt.req)
2349 if tt.expectTrusted {
2350 assert.Equal(t, 200, rw.Code)
2351 } else {
2352 assert.Equal(t, 403, rw.Code)
2354 })