git.iko.soy/typeable/oauth2-proxy
on v6.0.0 help sign in
typeable/oauth2-proxy/oauthproxy_test.go
1790 lines 53 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 "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 = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpamsK"
41func init() {
42 logger.SetFlags(logger.Lshortfile)
46type WebSocketOrRestHandler struct {
47 restHandler http.Handler
48 wsHandler http.Handler
51func (h *WebSocketOrRestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
52 if r.Header.Get("Upgrade") == "websocket" {
53 h.wsHandler.ServeHTTP(w, r)
54 } else {
55 h.restHandler.ServeHTTP(w, r)
56 }
59func TestWebSocketProxy(t *testing.T) {
60 handler := WebSocketOrRestHandler{
61 restHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
62 w.WriteHeader(200)
63 hostname, _, _ := net.SplitHostPort(r.Host)
64 w.Write([]byte(hostname))
65 }),
66 wsHandler: websocket.Handler(func(ws *websocket.Conn) {
67 defer ws.Close()
68 var data []byte
69 err := websocket.Message.Receive(ws, &data)
70 if err != nil {
71 t.Fatalf("err %s", err)
72 return
73 }
74 err = websocket.Message.Send(ws, data)
75 if err != nil {
76 t.Fatalf("err %s", err)
77 }
78 }),
79 }
80 backend := httptest.NewServer(&handler)
81 defer backend.Close()
83 backendURL, _ := url.Parse(backend.URL)
85 options := options.NewOptions()
86 var auth hmacauth.HmacAuth
87 options.PassHostHeader = true
88 proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, options, auth)
89 frontend := httptest.NewServer(proxyHandler)
90 defer frontend.Close()
92 frontendURL, _ := url.Parse(frontend.URL)
93 frontendWSURL := "ws://" + frontendURL.Host + "/"
95 ws, err := websocket.Dial(frontendWSURL, "", "http://localhost/")
96 if err != nil {
97 t.Fatalf("err %s", err)
98 }
99 request := []byte("hello, world!")
100 err = websocket.Message.Send(ws, request)
101 if err != nil {
102 t.Fatalf("err %s", err)
103 }
104 var response = make([]byte, 1024)
105 websocket.Message.Receive(ws, &response)
106 if err != nil {
107 t.Fatalf("err %s", err)
108 }
109 if g, e := string(request), string(response); g != e {
110 t.Errorf("got body %q; expected %q", g, e)
111 }
113 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
114 res, _ := http.DefaultClient.Do(getReq)
115 bodyBytes, _ := ioutil.ReadAll(res.Body)
116 backendHostname, _, _ := net.SplitHostPort(backendURL.Host)
117 if g, e := string(bodyBytes), backendHostname; g != e {
118 t.Errorf("got body %q; expected %q", g, e)
119 }
122func TestNewReverseProxy(t *testing.T) {
123 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
124 w.WriteHeader(200)
125 hostname, _, _ := net.SplitHostPort(r.Host)
126 w.Write([]byte(hostname))
127 }))
128 defer backend.Close()
130 backendURL, _ := url.Parse(backend.URL)
131 backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host)
132 backendHost := net.JoinHostPort(backendHostname, backendPort)
133 proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
135 proxyHandler := NewReverseProxy(proxyURL, &options.Options{FlushInterval: time.Second})
136 setProxyUpstreamHostHeader(proxyHandler, proxyURL)
137 frontend := httptest.NewServer(proxyHandler)
138 defer frontend.Close()
140 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
141 res, _ := http.DefaultClient.Do(getReq)
142 bodyBytes, _ := ioutil.ReadAll(res.Body)
143 if g, e := string(bodyBytes), backendHostname; g != e {
144 t.Errorf("got body %q; expected %q", g, e)
145 }
148func TestEncodedSlashes(t *testing.T) {
149 var seen string
150 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
151 w.WriteHeader(200)
152 seen = r.RequestURI
153 }))
154 defer backend.Close()
156 b, _ := url.Parse(backend.URL)
157 proxyHandler := NewReverseProxy(b, &options.Options{FlushInterval: time.Second})
158 setProxyDirector(proxyHandler)
159 frontend := httptest.NewServer(proxyHandler)
160 defer frontend.Close()
162 f, _ := url.Parse(frontend.URL)
163 encodedPath := "/a%2Fb/?c=1"
164 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: f.Host, Opaque: encodedPath}}
165 _, err := http.DefaultClient.Do(getReq)
166 if err != nil {
167 t.Fatalf("err %s", err)
168 }
169 if seen != encodedPath {
170 t.Errorf("got bad request %q expected %q", seen, encodedPath)
171 }
174func TestRobotsTxt(t *testing.T) {
175 opts := options.NewOptions()
176 opts.ClientID = "asdlkjx"
177 opts.ClientSecret = "alkgks"
178 opts.Cookie.Secret = rawCookieSecret
179 validation.Validate(opts)
181 proxy := NewOAuthProxy(opts, func(string) bool { return true })
182 rw := httptest.NewRecorder()
183 req, _ := http.NewRequest("GET", "/robots.txt", nil)
184 proxy.ServeHTTP(rw, req)
185 assert.Equal(t, 200, rw.Code)
186 assert.Equal(t, "User-agent: *\nDisallow: /", rw.Body.String())
189func TestIsValidRedirect(t *testing.T) {
190 opts := options.NewOptions()
191 opts.ClientID = "skdlfj"
192 opts.ClientSecret = "fgkdsgj"
193 opts.Cookie.Secret = base64CookieSecret
194 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
195 opts.WhitelistDomains = []string{
196 "foo.bar",
197 ".bar.foo",
198 "port.bar:8080",
199 ".sub.port.bar:8080",
200 "anyport.bar:*",
201 ".sub.anyport.bar:*",
202 }
203 validation.Validate(opts)
205 proxy := NewOAuthProxy(opts, func(string) bool { return true })
207 testCases := []struct {
208 Desc, Redirect string
209 ExpectedResult bool
210 }{
211 {
212 Desc: "noRD",
213 Redirect: "",
214 ExpectedResult: false,
215 },
216 {
217 Desc: "singleSlash",
218 Redirect: "/redirect",
219 ExpectedResult: true,
220 },
221 {
222 Desc: "doubleSlash",
223 Redirect: "//redirect",
224 ExpectedResult: false,
225 },
226 {
227 Desc: "validHTTP",
228 Redirect: "http://foo.bar/redirect",
229 ExpectedResult: true,
230 },
231 {
232 Desc: "validHTTPS",
233 Redirect: "https://foo.bar/redirect",
234 ExpectedResult: true,
235 },
236 {
237 Desc: "invalidHTTPSubdomain",
238 Redirect: "http://baz.foo.bar/redirect",
239 ExpectedResult: false,
240 },
241 {
242 Desc: "invalidHTTPSSubdomain",
243 Redirect: "https://baz.foo.bar/redirect",
244 ExpectedResult: false,
245 },
246 {
247 Desc: "validHTTPSubdomain",
248 Redirect: "http://baz.bar.foo/redirect",
249 ExpectedResult: true,
250 },
251 {
252 Desc: "validHTTPSSubdomain",
253 Redirect: "https://baz.bar.foo/redirect",
254 ExpectedResult: true,
255 },
256 {
257 Desc: "validHTTPDomain",
258 Redirect: "http://bar.foo/redirect",
259 ExpectedResult: true,
260 },
261 {
262 Desc: "invalidHTTP1",
263 Redirect: "http://foo.bar.evil.corp/redirect",
264 ExpectedResult: false,
265 },
266 {
267 Desc: "invalidHTTPS1",
268 Redirect: "https://foo.bar.evil.corp/redirect",
269 ExpectedResult: false,
270 },
271 {
272 Desc: "invalidHTTP2",
273 Redirect: "http://evil.corp/redirect?rd=foo.bar",
274 ExpectedResult: false,
275 },
276 {
277 Desc: "invalidHTTPS2",
278 Redirect: "https://evil.corp/redirect?rd=foo.bar",
279 ExpectedResult: false,
280 },
281 {
282 Desc: "invalidPort",
283 Redirect: "https://evil.corp:3838/redirect",
284 ExpectedResult: false,
285 },
286 {
287 Desc: "invalidEmptyPort",
288 Redirect: "http://foo.bar:3838/redirect",
289 ExpectedResult: false,
290 },
291 {
292 Desc: "invalidEmptyPortSubdomain",
293 Redirect: "http://baz.bar.foo:3838/redirect",
294 ExpectedResult: false,
295 },
296 {
297 Desc: "validSpecificPort",
298 Redirect: "http://port.bar:8080/redirect",
299 ExpectedResult: true,
300 },
301 {
302 Desc: "invalidSpecificPort",
303 Redirect: "http://port.bar:3838/redirect",
304 ExpectedResult: false,
305 },
306 {
307 Desc: "validSpecificPortSubdomain",
308 Redirect: "http://foo.sub.port.bar:8080/redirect",
309 ExpectedResult: true,
310 },
311 {
312 Desc: "invalidSpecificPortSubdomain",
313 Redirect: "http://foo.sub.port.bar:3838/redirect",
314 ExpectedResult: false,
315 },
316 {
317 Desc: "validAnyPort1",
318 Redirect: "http://anyport.bar:8080/redirect",
319 ExpectedResult: true,
320 },
321 {
322 Desc: "validAnyPort2",
323 Redirect: "http://anyport.bar:8081/redirect",
324 ExpectedResult: true,
325 },
326 {
327 Desc: "validAnyPortSubdomain1",
328 Redirect: "http://a.sub.anyport.bar:8080/redirect",
329 ExpectedResult: true,
330 },
331 {
332 Desc: "validAnyPortSubdomain2",
333 Redirect: "http://a.sub.anyport.bar:8081/redirect",
334 ExpectedResult: true,
335 },
336 {
337 Desc: "openRedirect1",
338 Redirect: "/\\evil.com",
339 ExpectedResult: false,
340 },
341 {
342 Desc: "openRedirectSpace1",
343 Redirect: "/ /evil.com",
344 ExpectedResult: false,
345 },
346 {
347 Desc: "openRedirectSpace2",
348 Redirect: "/ \\evil.com",
349 ExpectedResult: false,
350 },
351 {
352 Desc: "openRedirectTab1",
353 Redirect: "/\t/evil.com",
354 ExpectedResult: false,
355 },
356 {
357 Desc: "openRedirectTab2",
358 Redirect: "/\t\\evil.com",
359 ExpectedResult: false,
360 },
361 {
362 Desc: "openRedirectVerticalTab1",
363 Redirect: "/\v/evil.com",
364 ExpectedResult: false,
365 },
366 {
367 Desc: "openRedirectVerticalTab2",
368 Redirect: "/\v\\evil.com",
369 ExpectedResult: false,
370 },
371 {
372 Desc: "openRedirectNewLine1",
373 Redirect: "/\n/evil.com",
374 ExpectedResult: false,
375 },
376 {
377 Desc: "openRedirectNewLine2",
378 Redirect: "/\n\\evil.com",
379 ExpectedResult: false,
380 },
381 {
382 Desc: "openRedirectCarriageReturn1",
383 Redirect: "/\r/evil.com",
384 ExpectedResult: false,
385 },
386 {
387 Desc: "openRedirectCarriageReturn2",
388 Redirect: "/\r\\evil.com",
389 ExpectedResult: false,
390 },
391 {
392 Desc: "openRedirectTripleTab",
393 Redirect: "/\t\t/\t/evil.com",
394 ExpectedResult: false,
395 },
396 {
397 Desc: "openRedirectTripleTab2",
398 Redirect: "/\t\t\\\t/evil.com",
399 ExpectedResult: false,
400 },
401 {
402 Desc: "openRedirectQuadTab1",
403 Redirect: "/\t\t/\t\t\\evil.com",
404 ExpectedResult: false,
405 },
406 {
407 Desc: "openRedirectQuadTab2",
408 Redirect: "/\t\t\\\t\t/evil.com",
409 ExpectedResult: false,
410 },
411 {
412 Desc: "openRedirectPeriod1",
413 Redirect: "/./\\evil.com",
414 ExpectedResult: false,
415 },
416 {
417 Desc: "openRedirectPeriod2",
418 Redirect: "/./../../\\evil.com",
419 ExpectedResult: false,
420 },
421 {
422 Desc: "openRedirectDoubleTab",
423 Redirect: "/\t/\t\\evil.com",
424 ExpectedResult: false,
425 },
426 }
428 for _, tc := range testCases {
429 t.Run(tc.Desc, func(t *testing.T) {
430 result := proxy.IsValidRedirect(tc.Redirect)
432 if result != tc.ExpectedResult {
433 t.Errorf("expected %t got %t", tc.ExpectedResult, result)
434 }
435 })
436 }
439func TestOpenRedirects(t *testing.T) {
440 opts := options.NewOptions()
441 opts.ClientID = "skdlfj"
442 opts.ClientSecret = "fgkdsgj"
443 opts.Cookie.Secret = rawCookieSecret
444 opts.EmailDomains = []string{"*"}
445 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
446 opts.WhitelistDomains = []string{
447 "foo.bar",
448 ".bar.foo",
449 "port.bar:8080",
450 ".sub.port.bar:8080",
451 "anyport.bar:*",
452 ".sub.anyport.bar:*",
453 "www.whitelisteddomain.tld",
454 }
455 err := validation.Validate(opts)
456 if err != nil {
457 t.Fatal(err)
458 }
460 proxy := NewOAuthProxy(opts, func(string) bool { return true })
462 file, err := os.Open("./test/openredirects.txt")
463 if err != nil {
464 t.Fatal(err)
465 }
466 defer file.Close()
468 scanner := bufio.NewScanner(file)
469 for scanner.Scan() {
470 rd := scanner.Text()
471 t.Run(rd, func(t *testing.T) {
472 rdUnescaped, err := url.QueryUnescape(rd)
473 if err != nil {
474 t.Fatal(err)
475 }
476 if proxy.IsValidRedirect(rdUnescaped) {
477 t.Errorf("Expected %q to not be valid (unescaped: %q)", rd, rdUnescaped)
478 }
479 })
480 }
482 if err := scanner.Err(); err != nil {
483 t.Fatal(err)
484 }
487type TestProvider struct {
488 *providers.ProviderData
489 EmailAddress string
490 ValidToken bool
491 GroupValidator func(string) bool
494var _ providers.Provider = (*TestProvider)(nil)
496func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
497 return &TestProvider{
498 ProviderData: &providers.ProviderData{
499 ProviderName: "Test Provider",
500 LoginURL: &url.URL{
501 Scheme: "http",
502 Host: providerURL.Host,
503 Path: "/oauth/authorize",
504 },
505 RedeemURL: &url.URL{
506 Scheme: "http",
507 Host: providerURL.Host,
508 Path: "/oauth/token",
509 },
510 ProfileURL: &url.URL{
511 Scheme: "http",
512 Host: providerURL.Host,
513 Path: "/api/v1/profile",
514 },
515 Scope: "profile.email",
516 },
517 EmailAddress: emailAddress,
518 GroupValidator: func(s string) bool {
519 return true
520 },
521 }
524func (tp *TestProvider) GetEmailAddress(ctx context.Context, session *sessions.SessionState) (string, error) {
525 return tp.EmailAddress, nil
528func (tp *TestProvider) ValidateSessionState(ctx context.Context, session *sessions.SessionState) bool {
529 return tp.ValidToken
532func TestBasicAuthPassword(t *testing.T) {
533 providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
534 logger.Printf("%#v", r)
535 var payload string
536 switch r.URL.Path {
537 case "/oauth/token":
538 payload = `{"access_token": "my_auth_token"}`
539 default:
540 payload = r.Header.Get("Authorization")
541 if payload == "" {
542 payload = "No Authorization header found."
543 }
544 }
545 w.WriteHeader(200)
546 w.Write([]byte(payload))
547 }))
548 opts := options.NewOptions()
549 opts.Upstreams = append(opts.Upstreams, providerServer.URL)
550 // The CookieSecret must be 32 bytes in order to create the AES
551 // cipher.
552 opts.Cookie.Secret = "xyzzyplughxyzzyplughxyzzyplughxp"
553 opts.ClientID = "dlgkj"
554 opts.ClientSecret = "alkgret"
555 opts.Cookie.Secure = false
556 opts.PassBasicAuth = true
557 opts.SetBasicAuth = true
558 opts.PassUserHeaders = true
559 opts.PreferEmailToUser = true
560 opts.BasicAuthPassword = "This is a secure password"
561 validation.Validate(opts)
563 providerURL, _ := url.Parse(providerServer.URL)
564 const emailAddress = "john.doe@example.com"
566 opts.SetProvider(NewTestProvider(providerURL, emailAddress))
567 proxy := NewOAuthProxy(opts, func(email string) bool {
568 return email == emailAddress
569 })
571 rw := httptest.NewRecorder()
572 req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:", strings.NewReader(""))
573 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
574 proxy.ServeHTTP(rw, req)
575 if rw.Code >= 400 {
576 t.Fatalf("expected 3xx got %d", rw.Code)
577 }
578 cookie := rw.Header().Values("Set-Cookie")[1]
580 cookieName := proxy.CookieName
581 var value string
582 keyPrefix := cookieName + "="
584 for _, field := range strings.Split(cookie, "; ") {
585 value = strings.TrimPrefix(field, keyPrefix)
586 if value != field {
587 break
588 } else {
589 value = ""
590 }
591 }
593 req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
594 req.AddCookie(&http.Cookie{
595 Name: cookieName,
596 Value: value,
597 Path: "/",
598 Expires: time.Now().Add(time.Duration(24)),
599 HttpOnly: true,
600 })
601 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
603 rw = httptest.NewRecorder()
604 proxy.ServeHTTP(rw, req)
606 // The username in the basic auth credentials is expected to be equal to the email address from the
607 // auth response, so we use the same variable here.
608 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
609 assert.Equal(t, expectedHeader, rw.Body.String())
610 providerServer.Close()
613func TestBasicAuthWithEmail(t *testing.T) {
614 opts := options.NewOptions()
615 opts.PassBasicAuth = true
616 opts.PassUserHeaders = false
617 opts.PreferEmailToUser = false
618 opts.BasicAuthPassword = "This is a secure password"
619 validation.Validate(opts)
621 const emailAddress = "john.doe@example.com"
622 const userName = "9fcab5c9b889a557"
624 // The username in the basic auth credentials is expected to be equal to the email address from the
625 expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
626 expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
628 created := time.Now()
629 session := &sessions.SessionState{
630 User: userName,
631 Email: emailAddress,
632 AccessToken: "oauth_token",
633 CreatedAt: &created,
634 }
635 {
636 rw := httptest.NewRecorder()
637 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
638 proxy := NewOAuthProxy(opts, func(email string) bool {
639 return email == emailAddress
640 })
641 proxy.addHeadersForProxying(rw, req, session)
642 assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
643 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
644 }
646 opts.PreferEmailToUser = true
647 {
648 rw := httptest.NewRecorder()
649 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
651 proxy := NewOAuthProxy(opts, func(email string) bool {
652 return email == emailAddress
653 })
654 proxy.addHeadersForProxying(rw, req, session)
655 assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
656 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
657 }
660func TestPassUserHeadersWithEmail(t *testing.T) {
661 opts := options.NewOptions()
662 opts.PassBasicAuth = false
663 opts.PassUserHeaders = true
664 opts.PreferEmailToUser = false
665 validation.Validate(opts)
667 const emailAddress = "john.doe@example.com"
668 const userName = "9fcab5c9b889a557"
670 created := time.Now()
671 session := &sessions.SessionState{
672 User: userName,
673 Email: emailAddress,
674 AccessToken: "oauth_token",
675 CreatedAt: &created,
676 }
677 {
678 rw := httptest.NewRecorder()
679 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
680 proxy := NewOAuthProxy(opts, func(email string) bool {
681 return email == emailAddress
682 })
683 proxy.addHeadersForProxying(rw, req, session)
684 assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
685 }
687 opts.PreferEmailToUser = true
688 {
689 rw := httptest.NewRecorder()
690 req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
692 proxy := NewOAuthProxy(opts, func(email string) bool {
693 return email == emailAddress
694 })
695 proxy.addHeadersForProxying(rw, req, session)
696 assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
697 }
700type PassAccessTokenTest struct {
701 providerServer *httptest.Server
702 proxy *OAuthProxy
703 opts *options.Options
706type PassAccessTokenTestOptions struct {
707 PassAccessToken bool
708 ProxyUpstream string
711func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
712 t := &PassAccessTokenTest{}
714 t.providerServer = httptest.NewServer(
715 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
716 var payload string
717 switch r.URL.Path {
718 case "/oauth/token":
719 payload = `{"access_token": "my_auth_token"}`
720 default:
721 payload = r.Header.Get("X-Forwarded-Access-Token")
722 if payload == "" {
723 payload = "No access token found."
724 }
725 }
726 w.WriteHeader(200)
727 w.Write([]byte(payload))
728 }))
730 t.opts = options.NewOptions()
731 t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL)
732 if opts.ProxyUpstream != "" {
733 t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream)
734 }
735 // The CookieSecret must be 32 bytes in order to create the AES
736 // cipher.
737 t.opts.Cookie.Secret = "xyzzyplughxyzzyplughxyzzyplughxp"
738 t.opts.ClientID = "slgkj"
739 t.opts.ClientSecret = "gfjgojl"
740 t.opts.Cookie.Secure = false
741 t.opts.PassAccessToken = opts.PassAccessToken
742 validation.Validate(t.opts)
744 providerURL, _ := url.Parse(t.providerServer.URL)
745 const emailAddress = "michael.bland@gsa.gov"
747 t.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
748 t.proxy = NewOAuthProxy(t.opts, func(email string) bool {
749 return email == emailAddress
750 })
751 return t
754func (patTest *PassAccessTokenTest) Close() {
755 patTest.providerServer.Close()
758func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
759 cookie string) {
760 rw := httptest.NewRecorder()
761 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
762 strings.NewReader(""))
763 if err != nil {
764 return 0, ""
765 }
766 req.AddCookie(patTest.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
767 patTest.proxy.ServeHTTP(rw, req)
768 return rw.Code, rw.Header().Values("Set-Cookie")[1]
771// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
772// and cookie and returns body and status code.
773func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
774 cookieName := patTest.proxy.CookieName
775 var value string
776 keyPrefix := cookieName + "="
778 for _, field := range strings.Split(cookie, "; ") {
779 value = strings.TrimPrefix(field, keyPrefix)
780 if value != field {
781 break
782 } else {
783 value = ""
784 }
785 }
786 if value == "" {
787 return 0, ""
788 }
790 req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
791 if err != nil {
792 return 0, ""
793 }
794 req.AddCookie(&http.Cookie{
795 Name: cookieName,
796 Value: value,
797 Path: "/",
798 Expires: time.Now().Add(time.Duration(24)),
799 HttpOnly: true,
800 })
802 rw := httptest.NewRecorder()
803 patTest.proxy.ServeHTTP(rw, req)
804 return rw.Code, rw.Body.String()
807func TestForwardAccessTokenUpstream(t *testing.T) {
808 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
809 PassAccessToken: true,
810 })
811 defer patTest.Close()
813 // A successful validation will redirect and set the auth cookie.
814 code, cookie := patTest.getCallbackEndpoint()
815 if code != 302 {
816 t.Fatalf("expected 302; got %d", code)
817 }
818 assert.NotEqual(t, nil, cookie)
820 // Now we make a regular request; the access_token from the cookie is
821 // forwarded as the "X-Forwarded-Access-Token" header. The token is
822 // read by the test provider server and written in the response body.
823 code, payload := patTest.getEndpointWithCookie(cookie, "/")
824 if code != 200 {
825 t.Fatalf("expected 200; got %d", code)
826 }
827 assert.Equal(t, "my_auth_token", payload)
830func TestStaticProxyUpstream(t *testing.T) {
831 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
832 PassAccessToken: true,
833 ProxyUpstream: "static://200/static-proxy",
834 })
836 defer patTest.Close()
838 // A successful validation will redirect and set the auth cookie.
839 code, cookie := patTest.getCallbackEndpoint()
840 if code != 302 {
841 t.Fatalf("expected 302; got %d", code)
842 }
843 assert.NotEqual(t, nil, cookie)
845 // Now we make a regular request against the upstream proxy; And validate
846 // the returned status code through the static proxy.
847 code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
848 if code != 200 {
849 t.Fatalf("expected 200; got %d", code)
850 }
851 assert.Equal(t, "Authenticated", payload)
854func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
855 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
856 PassAccessToken: false,
857 })
858 defer patTest.Close()
860 // A successful validation will redirect and set the auth cookie.
861 code, cookie := patTest.getCallbackEndpoint()
862 if code != 302 {
863 t.Fatalf("expected 302; got %d", code)
864 }
865 assert.NotEqual(t, nil, cookie)
867 // Now we make a regular request, but the access token header should
868 // not be present.
869 code, payload := patTest.getEndpointWithCookie(cookie, "/")
870 if code != 200 {
871 t.Fatalf("expected 200; got %d", code)
872 }
873 assert.Equal(t, "No access token found.", payload)
876type SignInPageTest struct {
877 opts *options.Options
878 proxy *OAuthProxy
879 signInRegexp *regexp.Regexp
880 signInProviderRegexp *regexp.Regexp
883const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
884const signInSkipProvider = `>Found<`
886func NewSignInPageTest(skipProvider bool) *SignInPageTest {
887 var sipTest SignInPageTest
889 sipTest.opts = options.NewOptions()
890 sipTest.opts.Cookie.Secret = rawCookieSecret
891 sipTest.opts.ClientID = "lkdgj"
892 sipTest.opts.ClientSecret = "sgiufgoi"
893 sipTest.opts.SkipProviderButton = skipProvider
894 validation.Validate(sipTest.opts)
896 sipTest.proxy = NewOAuthProxy(sipTest.opts, func(email string) bool {
897 return true
898 })
899 sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
900 sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
902 return &sipTest
905func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
906 rw := httptest.NewRecorder()
907 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
908 sipTest.proxy.ServeHTTP(rw, req)
909 return rw.Code, rw.Body.String()
912func TestSignInPageIncludesTargetRedirect(t *testing.T) {
913 sipTest := NewSignInPageTest(false)
914 const endpoint = "/some/random/endpoint"
916 code, body := sipTest.GetEndpoint(endpoint)
917 assert.Equal(t, 403, code)
919 match := sipTest.signInRegexp.FindStringSubmatch(body)
920 if match == nil {
921 t.Fatal("Did not find pattern in body: " +
922 signInRedirectPattern + "\nBody:\n" + body)
923 }
924 if match[1] != endpoint {
925 t.Fatal(`expected redirect to "` + endpoint +
926 `", but was "` + match[1] + `"`)
927 }
930func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
931 sipTest := NewSignInPageTest(false)
932 code, body := sipTest.GetEndpoint("/oauth2/sign_in")
933 assert.Equal(t, 200, code)
935 match := sipTest.signInRegexp.FindStringSubmatch(body)
936 if match == nil {
937 t.Fatal("Did not find pattern in body: " +
938 signInRedirectPattern + "\nBody:\n" + body)
939 }
940 if match[1] != "/" {
941 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
942 }
945func TestSignInPageSkipProvider(t *testing.T) {
946 sipTest := NewSignInPageTest(true)
947 const endpoint = "/some/random/endpoint"
949 code, body := sipTest.GetEndpoint(endpoint)
950 assert.Equal(t, 302, code)
952 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
953 if match == nil {
954 t.Fatal("Did not find pattern in body: " +
955 signInSkipProvider + "\nBody:\n" + body)
956 }
959func TestSignInPageSkipProviderDirect(t *testing.T) {
960 sipTest := NewSignInPageTest(true)
961 const endpoint = "/sign_in"
963 code, body := sipTest.GetEndpoint(endpoint)
964 assert.Equal(t, 302, code)
966 match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
967 if match == nil {
968 t.Fatal("Did not find pattern in body: " +
969 signInSkipProvider + "\nBody:\n" + body)
970 }
973type ProcessCookieTest struct {
974 opts *options.Options
975 proxy *OAuthProxy
976 rw *httptest.ResponseRecorder
977 req *http.Request
978 validateUser bool
981type ProcessCookieTestOpts struct {
982 providerValidateCookieResponse bool
985type OptionsModifier func(*options.Options)
987func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) *ProcessCookieTest {
988 var pcTest ProcessCookieTest
990 pcTest.opts = options.NewOptions()
991 for _, modifier := range modifiers {
992 modifier(pcTest.opts)
993 }
994 pcTest.opts.ClientID = "asdfljk"
995 pcTest.opts.ClientSecret = "lkjfdsig"
996 pcTest.opts.Cookie.Secret = "0123456789abcdef0123456789abcdef"
997 // First, set the CookieRefresh option so proxy.AesCipher is created,
998 // needed to encrypt the access_token.
999 pcTest.opts.Cookie.Refresh = time.Hour
1000 validation.Validate(pcTest.opts)
1002 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1003 return pcTest.validateUser
1004 })
1005 pcTest.proxy.provider = &TestProvider{
1006 ValidToken: opts.providerValidateCookieResponse,
1009 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
1010 // access_token validation.
1011 pcTest.proxy.CookieRefresh = time.Duration(0)
1012 pcTest.rw = httptest.NewRecorder()
1013 pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
1014 pcTest.validateUser = true
1015 return &pcTest
1018func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
1019 return NewProcessCookieTest(ProcessCookieTestOpts{
1020 providerValidateCookieResponse: true,
1021 })
1024func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) *ProcessCookieTest {
1025 return NewProcessCookieTest(ProcessCookieTestOpts{
1026 providerValidateCookieResponse: true,
1027 }, modifiers...)
1030func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
1031 err := p.proxy.SaveSession(p.rw, p.req, s)
1032 if err != nil {
1033 return err
1035 for _, cookie := range p.rw.Result().Cookies() {
1036 p.req.AddCookie(cookie)
1038 return nil
1041func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
1042 return p.proxy.LoadCookiedSession(p.req)
1045func TestLoadCookiedSession(t *testing.T) {
1046 pcTest := NewProcessCookieTestWithDefaults()
1048 created := time.Now()
1049 startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
1050 pcTest.SaveSession(startSession)
1052 session, err := pcTest.LoadCookiedSession()
1053 assert.Equal(t, nil, err)
1054 assert.Equal(t, startSession.Email, session.Email)
1055 assert.Equal(t, "", session.User)
1056 assert.Equal(t, startSession.AccessToken, session.AccessToken)
1059func TestProcessCookieNoCookieError(t *testing.T) {
1060 pcTest := NewProcessCookieTestWithDefaults()
1062 session, err := pcTest.LoadCookiedSession()
1063 assert.Equal(t, "cookie \"_oauth2_proxy\" not present", err.Error())
1064 if session != nil {
1065 t.Errorf("expected nil session. got %#v", session)
1069func TestProcessCookieRefreshNotSet(t *testing.T) {
1070 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1071 opts.Cookie.Expire = time.Duration(23) * time.Hour
1072 })
1073 reference := time.Now().Add(time.Duration(-2) * time.Hour)
1075 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1076 pcTest.SaveSession(startSession)
1078 session, err := pcTest.LoadCookiedSession()
1079 assert.Equal(t, nil, err)
1080 if session.Age() < time.Duration(-2)*time.Hour {
1081 t.Errorf("cookie too young %v", session.Age())
1083 assert.Equal(t, startSession.Email, session.Email)
1086func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1087 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1088 opts.Cookie.Expire = time.Duration(24) * time.Hour
1089 })
1090 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1091 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1092 pcTest.SaveSession(startSession)
1094 session, err := pcTest.LoadCookiedSession()
1095 assert.NotEqual(t, nil, err)
1096 if session != nil {
1097 t.Errorf("expected nil session %#v", session)
1101func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1102 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
1103 opts.Cookie.Expire = time.Duration(24) * time.Hour
1104 })
1105 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1106 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1107 pcTest.SaveSession(startSession)
1109 pcTest.proxy.CookieRefresh = time.Hour
1110 session, err := pcTest.LoadCookiedSession()
1111 assert.NotEqual(t, nil, err)
1112 if session != nil {
1113 t.Errorf("expected nil session %#v", session)
1117func NewUserInfoEndpointTest() *ProcessCookieTest {
1118 pcTest := NewProcessCookieTestWithDefaults()
1119 pcTest.req, _ = http.NewRequest("GET",
1120 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1121 return pcTest
1124func TestUserInfoEndpointAccepted(t *testing.T) {
1125 test := NewUserInfoEndpointTest()
1126 startSession := &sessions.SessionState{
1127 Email: "john.doe@example.com", AccessToken: "my_access_token"}
1128 test.SaveSession(startSession)
1130 test.proxy.ServeHTTP(test.rw, test.req)
1131 assert.Equal(t, http.StatusOK, test.rw.Code)
1132 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1133 assert.Equal(t, "{\"email\":\"john.doe@example.com\"}\n", string(bodyBytes))
1136func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1137 test := NewUserInfoEndpointTest()
1139 test.proxy.ServeHTTP(test.rw, test.req)
1140 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1143func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) *ProcessCookieTest {
1144 pcTest := NewProcessCookieTestWithOptionsModifiers(modifiers...)
1145 pcTest.req, _ = http.NewRequest("GET",
1146 pcTest.opts.ProxyPrefix+"/auth", nil)
1147 return pcTest
1150func TestAuthOnlyEndpointAccepted(t *testing.T) {
1151 test := NewAuthOnlyEndpointTest()
1152 created := time.Now()
1153 startSession := &sessions.SessionState{
1154 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1155 test.SaveSession(startSession)
1157 test.proxy.ServeHTTP(test.rw, test.req)
1158 assert.Equal(t, http.StatusAccepted, test.rw.Code)
1159 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1160 assert.Equal(t, "", string(bodyBytes))
1163func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1164 test := NewAuthOnlyEndpointTest()
1166 test.proxy.ServeHTTP(test.rw, test.req)
1167 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1168 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1169 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1172func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1173 test := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1174 opts.Cookie.Expire = time.Duration(24) * time.Hour
1175 })
1176 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1177 startSession := &sessions.SessionState{
1178 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1179 test.SaveSession(startSession)
1181 test.proxy.ServeHTTP(test.rw, test.req)
1182 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1183 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1184 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1187func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1188 test := NewAuthOnlyEndpointTest()
1189 created := time.Now()
1190 startSession := &sessions.SessionState{
1191 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1192 test.SaveSession(startSession)
1193 test.validateUser = false
1195 test.proxy.ServeHTTP(test.rw, test.req)
1196 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
1197 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
1198 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
1201func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1202 var pcTest ProcessCookieTest
1204 pcTest.opts = options.NewOptions()
1205 pcTest.opts.SetXAuthRequest = true
1206 validation.Validate(pcTest.opts)
1208 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1209 return pcTest.validateUser
1210 })
1211 pcTest.proxy.provider = &TestProvider{
1212 ValidToken: true,
1215 pcTest.validateUser = true
1217 pcTest.rw = httptest.NewRecorder()
1218 pcTest.req, _ = http.NewRequest("GET",
1219 pcTest.opts.ProxyPrefix+"/auth", nil)
1221 created := time.Now()
1222 startSession := &sessions.SessionState{
1223 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1224 pcTest.SaveSession(startSession)
1226 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1227 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1228 assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
1229 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
1232func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1233 var pcTest ProcessCookieTest
1235 pcTest.opts = options.NewOptions()
1236 pcTest.opts.SetXAuthRequest = true
1237 pcTest.opts.SetBasicAuth = true
1238 validation.Validate(pcTest.opts)
1240 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1241 return pcTest.validateUser
1242 })
1243 pcTest.proxy.provider = &TestProvider{
1244 ValidToken: true,
1247 pcTest.validateUser = true
1249 pcTest.rw = httptest.NewRecorder()
1250 pcTest.req, _ = http.NewRequest("GET",
1251 pcTest.opts.ProxyPrefix+"/auth", nil)
1253 created := time.Now()
1254 startSession := &sessions.SessionState{
1255 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1256 pcTest.SaveSession(startSession)
1258 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1259 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1260 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1261 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1262 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:"+pcTest.opts.BasicAuthPassword))
1263 assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
1266func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1267 var pcTest ProcessCookieTest
1269 pcTest.opts = options.NewOptions()
1270 pcTest.opts.SetXAuthRequest = true
1271 pcTest.opts.SetBasicAuth = false
1272 validation.Validate(pcTest.opts)
1274 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
1275 return pcTest.validateUser
1276 })
1277 pcTest.proxy.provider = &TestProvider{
1278 ValidToken: true,
1281 pcTest.validateUser = true
1283 pcTest.rw = httptest.NewRecorder()
1284 pcTest.req, _ = http.NewRequest("GET",
1285 pcTest.opts.ProxyPrefix+"/auth", nil)
1287 created := time.Now()
1288 startSession := &sessions.SessionState{
1289 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1290 pcTest.SaveSession(startSession)
1292 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1293 assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
1294 assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
1295 assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
1296 assert.Equal(t, 0, len(pcTest.rw.Header().Values("Authorization")), "should not have Authorization header entries")
1299func TestAuthSkippedForPreflightRequests(t *testing.T) {
1300 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1301 w.WriteHeader(200)
1302 w.Write([]byte("response"))
1303 }))
1304 defer upstream.Close()
1306 opts := options.NewOptions()
1307 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1308 opts.ClientID = "aljsal"
1309 opts.ClientSecret = "jglkfsdgj"
1310 opts.Cookie.Secret = base64CookieSecret
1311 opts.SkipAuthPreflight = true
1312 validation.Validate(opts)
1314 upstreamURL, _ := url.Parse(upstream.URL)
1315 opts.SetProvider(NewTestProvider(upstreamURL, ""))
1317 proxy := NewOAuthProxy(opts, func(string) bool { return false })
1318 rw := httptest.NewRecorder()
1319 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1320 proxy.ServeHTTP(rw, req)
1322 assert.Equal(t, 200, rw.Code)
1323 assert.Equal(t, "response", rw.Body.String())
1326type SignatureAuthenticator struct {
1327 auth hmacauth.HmacAuth
1330func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
1331 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
1332 if result == hmacauth.ResultNoSignature {
1333 w.Write([]byte("no signature received"))
1334 } else if result == hmacauth.ResultMatch {
1335 w.Write([]byte("signatures match"))
1336 } else if result == hmacauth.ResultMismatch {
1337 w.Write([]byte("signatures do not match:" +
1338 "\n received: " + headerSig +
1339 "\n computed: " + computedSig))
1340 } else {
1341 panic("Unknown result value: " + result.String())
1345type SignatureTest struct {
1346 opts *options.Options
1347 upstream *httptest.Server
1348 upstreamHost string
1349 provider *httptest.Server
1350 header http.Header
1351 rw *httptest.ResponseRecorder
1352 authenticator *SignatureAuthenticator
1355func NewSignatureTest() *SignatureTest {
1356 opts := options.NewOptions()
1357 opts.Cookie.Secret = rawCookieSecret
1358 opts.ClientID = "client ID"
1359 opts.ClientSecret = "client secret"
1360 opts.EmailDomains = []string{"acm.org"}
1362 authenticator := &SignatureAuthenticator{}
1363 upstream := httptest.NewServer(
1364 http.HandlerFunc(authenticator.Authenticate))
1365 upstreamURL, _ := url.Parse(upstream.URL)
1366 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1368 providerHandler := func(w http.ResponseWriter, r *http.Request) {
1369 w.Write([]byte(`{"access_token": "my_auth_token"}`))
1371 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
1372 providerURL, _ := url.Parse(provider.URL)
1373 opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
1375 return &SignatureTest{
1376 opts,
1377 upstream,
1378 upstreamURL.Host,
1379 provider,
1380 make(http.Header),
1381 httptest.NewRecorder(),
1382 authenticator,
1386func (st *SignatureTest) Close() {
1387 st.provider.Close()
1388 st.upstream.Close()
1391// fakeNetConn simulates an http.Request.Body buffer that will be consumed
1392// when it is read by the hmacauth.HmacAuth if not handled properly. See:
1393// https://github.com/18F/hmacauth/pull/4
1394type fakeNetConn struct {
1395 reqBody string
1398func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
1399 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
1400 copy(p, fnc.reqBody)
1401 fnc.reqBody = ""
1402 return bodyLen, io.EOF
1404 return 0, io.EOF
1407func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
1408 err := validation.Validate(st.opts)
1409 if err != nil {
1410 panic(err)
1412 proxy := NewOAuthProxy(st.opts, func(email string) bool { return true })
1414 var bodyBuf io.ReadCloser
1415 if body != "" {
1416 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
1418 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
1419 req.Header = st.header
1421 state := &sessions.SessionState{
1422 Email: "mbland@acm.org", AccessToken: "my_access_token"}
1423 err = proxy.SaveSession(st.rw, req, state)
1424 if err != nil {
1425 panic(err)
1427 for _, c := range st.rw.Result().Cookies() {
1428 req.AddCookie(c)
1430 // This is used by the upstream to validate the signature.
1431 st.authenticator.auth = hmacauth.NewHmacAuth(
1432 crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
1433 proxy.ServeHTTP(st.rw, req)
1436func TestNoRequestSignature(t *testing.T) {
1437 st := NewSignatureTest()
1438 defer st.Close()
1439 st.MakeRequestWithExpectedKey("GET", "", "")
1440 assert.Equal(t, 200, st.rw.Code)
1441 assert.Equal(t, st.rw.Body.String(), "no signature received")
1444func TestRequestSignatureGetRequest(t *testing.T) {
1445 st := NewSignatureTest()
1446 defer st.Close()
1447 st.opts.SignatureKey = "sha1:7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d"
1448 st.MakeRequestWithExpectedKey("GET", "", "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d")
1449 assert.Equal(t, 200, st.rw.Code)
1450 assert.Equal(t, st.rw.Body.String(), "signatures match")
1453func TestRequestSignaturePostRequest(t *testing.T) {
1454 st := NewSignatureTest()
1455 defer st.Close()
1456 st.opts.SignatureKey = "sha1:d90df39e2d19282840252612dd7c81421a372f61"
1457 payload := `{ "hello": "world!" }`
1458 st.MakeRequestWithExpectedKey("POST", payload, "d90df39e2d19282840252612dd7c81421a372f61")
1459 assert.Equal(t, 200, st.rw.Code)
1460 assert.Equal(t, st.rw.Body.String(), "signatures match")
1463func TestGetRedirect(t *testing.T) {
1464 options := options.NewOptions()
1465 _ = validation.Validate(options)
1466 require.NotEmpty(t, options.ProxyPrefix)
1467 proxy := NewOAuthProxy(options, func(s string) bool { return false })
1469 tests := []struct {
1470 name string
1471 url string
1472 expectedRedirect string
1473 }{
1475 name: "request outside of ProxyPrefix redirects to original URL",
1476 url: "/foo/bar",
1477 expectedRedirect: "/foo/bar",
1478 },
1480 name: "request under ProxyPrefix redirects to root",
1481 url: proxy.ProxyPrefix + "/foo/bar",
1482 expectedRedirect: "/",
1483 },
1485 for _, tt := range tests {
1486 t.Run(tt.name, func(t *testing.T) {
1487 req, _ := http.NewRequest("GET", tt.url, nil)
1488 redirect, err := proxy.GetRedirect(req)
1490 assert.NoError(t, err)
1491 assert.Equal(t, tt.expectedRedirect, redirect)
1492 })
1496type ajaxRequestTest struct {
1497 opts *options.Options
1498 proxy *OAuthProxy
1501func newAjaxRequestTest() *ajaxRequestTest {
1502 test := &ajaxRequestTest{}
1503 test.opts = options.NewOptions()
1504 test.opts.Cookie.Secret = base64CookieSecret
1505 test.opts.ClientID = "gkljfdl"
1506 test.opts.ClientSecret = "sdflkjs"
1507 validation.Validate(test.opts)
1508 test.proxy = NewOAuthProxy(test.opts, func(email string) bool {
1509 return true
1510 })
1511 return test
1514func (test *ajaxRequestTest) getEndpoint(endpoint string, header http.Header) (int, http.Header, error) {
1515 rw := httptest.NewRecorder()
1516 req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
1517 if err != nil {
1518 return 0, nil, err
1520 req.Header = header
1521 test.proxy.ServeHTTP(rw, req)
1522 return rw.Code, rw.Header(), nil
1525func testAjaxUnauthorizedRequest(t *testing.T, header http.Header) {
1526 test := newAjaxRequestTest()
1527 endpoint := "/test"
1529 code, rh, err := test.getEndpoint(endpoint, header)
1530 assert.NoError(t, err)
1531 assert.Equal(t, http.StatusUnauthorized, code)
1532 mime := rh.Get("Content-Type")
1533 assert.Equal(t, applicationJSON, mime)
1535func TestAjaxUnauthorizedRequest1(t *testing.T) {
1536 header := make(http.Header)
1537 header.Add("accept", applicationJSON)
1539 testAjaxUnauthorizedRequest(t, header)
1542func TestAjaxUnauthorizedRequest2(t *testing.T) {
1543 header := make(http.Header)
1544 header.Add("Accept", applicationJSON)
1546 testAjaxUnauthorizedRequest(t, header)
1549func TestAjaxForbiddendRequest(t *testing.T) {
1550 test := newAjaxRequestTest()
1551 endpoint := "/test"
1552 header := make(http.Header)
1553 code, rh, err := test.getEndpoint(endpoint, header)
1554 assert.NoError(t, err)
1555 assert.Equal(t, http.StatusForbidden, code)
1556 mime := rh.Get("Content-Type")
1557 assert.NotEqual(t, applicationJSON, mime)
1560func TestClearSplitCookie(t *testing.T) {
1561 opts := options.NewOptions()
1562 opts.Cookie.Name = "oauth2"
1563 opts.Cookie.Domains = []string{"abc"}
1564 store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1565 assert.Equal(t, err, nil)
1566 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1567 var rw = httptest.NewRecorder()
1568 req := httptest.NewRequest("get", "/", nil)
1570 req.AddCookie(&http.Cookie{
1571 Name: "test1",
1572 Value: "test1",
1573 })
1574 req.AddCookie(&http.Cookie{
1575 Name: "oauth2_0",
1576 Value: "oauth2_0",
1577 })
1578 req.AddCookie(&http.Cookie{
1579 Name: "oauth2_1",
1580 Value: "oauth2_1",
1581 })
1583 p.ClearSessionCookie(rw, req)
1584 header := rw.Header()
1586 assert.Equal(t, 2, len(header["Set-Cookie"]), "should have 3 set-cookie header entries")
1589func TestClearSingleCookie(t *testing.T) {
1590 opts := options.NewOptions()
1591 opts.Cookie.Name = "oauth2"
1592 opts.Cookie.Domains = []string{"abc"}
1593 store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1594 assert.Equal(t, err, nil)
1595 p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1596 var rw = httptest.NewRecorder()
1597 req := httptest.NewRequest("get", "/", nil)
1599 req.AddCookie(&http.Cookie{
1600 Name: "test1",
1601 Value: "test1",
1602 })
1603 req.AddCookie(&http.Cookie{
1604 Name: "oauth2",
1605 Value: "oauth2",
1606 })
1608 p.ClearSessionCookie(rw, req)
1609 header := rw.Header()
1611 assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
1614type NoOpKeySet struct {
1617func (NoOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
1618 splitStrings := strings.Split(jwt, ".")
1619 payloadString := splitStrings[1]
1620 return base64.RawURLEncoding.DecodeString(payloadString)
1623func TestGetJwtSession(t *testing.T) {
1624 /* token payload:
1626 "sub": "1234567890",
1627 "aud": "https://test.myapp.com",
1628 "name": "John Doe",
1629 "email": "john@example.com",
1630 "iss": "https://issuer.example.com",
1631 "iat": 1553691215,
1632 "exp": 1912151821
1634 */
1635 goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
1636 "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
1637 "WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
1638 "E1LCJleHAiOjE5MTIxNTE4MjF9." +
1639 "rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
1640 "OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
1642 keyset := NoOpKeySet{}
1643 verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
1644 &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
1646 test := NewAuthOnlyEndpointTest(func(opts *options.Options) {
1647 opts.PassAuthorization = true
1648 opts.SetAuthorization = true
1649 opts.SetXAuthRequest = true
1650 opts.SkipJwtBearerTokens = true
1651 opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
1652 })
1653 tp, _ := test.proxy.provider.(*TestProvider)
1654 tp.GroupValidator = func(s string) bool {
1655 return true
1658 authHeader := fmt.Sprintf("Bearer %s", goodJwt)
1659 test.req.Header = map[string][]string{
1660 "Authorization": {authHeader},
1663 // Bearer
1664 expires := time.Unix(1912151821, 0)
1665 session, _ := test.proxy.GetJwtSession(test.req)
1666 assert.Equal(t, session.User, "1234567890")
1667 assert.Equal(t, session.Email, "john@example.com")
1668 assert.Equal(t, session.ExpiresOn, &expires)
1669 assert.Equal(t, session.IDToken, goodJwt)
1671 test.proxy.ServeHTTP(test.rw, test.req)
1672 if test.rw.Code >= 400 {
1673 t.Fatalf("expected 3xx got %d", test.rw.Code)
1676 // Check PassAuthorization, should overwrite Basic header
1677 assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
1678 assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "1234567890")
1679 assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
1681 // SetAuthorization and SetXAuthRequest
1682 assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
1683 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "1234567890")
1684 assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
1687func TestFindJwtBearerToken(t *testing.T) {
1688 p := OAuthProxy{CookieName: "oauth2", CookieDomains: []string{"abc"}}
1689 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}}
1691 validToken := "eyJfoobar.eyJfoobar.12345asdf"
1692 var token string
1694 // Bearer
1695 getReq.Header = map[string][]string{
1696 "Authorization": {fmt.Sprintf("Bearer %s", validToken)},
1699 token, _ = p.findBearerToken(getReq)
1700 assert.Equal(t, validToken, token)
1702 // Basic - no password
1703 getReq.SetBasicAuth(token, "")
1704 token, _ = p.findBearerToken(getReq)
1705 assert.Equal(t, validToken, token)
1707 // Basic - sentinel password
1708 getReq.SetBasicAuth(token, "x-oauth-basic")
1709 token, _ = p.findBearerToken(getReq)
1710 assert.Equal(t, validToken, token)
1712 // Basic - any username, password matching jwt pattern
1713 getReq.SetBasicAuth("any-username-you-could-wish-for", token)
1714 token, _ = p.findBearerToken(getReq)
1715 assert.Equal(t, validToken, token)
1717 failures := []string{
1718 // Too many parts
1719 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1720 // Not enough parts
1721 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA",
1722 // Invalid encrypted key
1723 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.//////.dGVzdA.dGVzdA.dGVzdA",
1724 // Invalid IV
1725 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.//////.dGVzdA.dGVzdA",
1726 // Invalid ciphertext
1727 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.//////.dGVzdA",
1728 // Invalid tag
1729 "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.//////",
1730 // Invalid header
1731 "W10.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1732 // Invalid header
1733 "######.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1734 // Missing alc/enc params
1735 "e30.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
1738 for _, failure := range failures {
1739 getReq.Header = map[string][]string{
1740 "Authorization": {fmt.Sprintf("Bearer %s", failure)},
1742 _, err := p.findBearerToken(getReq)
1743 assert.Error(t, err)
1746 fmt.Printf("%s", token)
1749func Test_prepareNoCache(t *testing.T) {
1750 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1751 prepareNoCache(w)
1752 })
1753 mux := http.NewServeMux()
1754 mux.Handle("/", handler)
1756 rec := httptest.NewRecorder()
1757 req := httptest.NewRequest(http.MethodGet, "/", nil)
1758 mux.ServeHTTP(rec, req)
1760 for k, v := range noCacheHeaders {
1761 assert.Equal(t, rec.Header().Get(k), v)
1765func Test_noCacheHeadersDoesNotExistsInResponseHeadersFromUpstream(t *testing.T) {
1766 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1767 w.Write([]byte("upstream"))
1768 }))
1769 t.Cleanup(upstream.Close)
1771 opts := options.NewOptions()
1772 opts.Upstreams = []string{upstream.URL}
1773 opts.SkipAuthRegex = []string{".*"}
1774 _ = validation.Validate(opts)
1775 proxy := NewOAuthProxy(opts, func(email string) bool {
1776 return true
1777 })
1779 rec := httptest.NewRecorder()
1780 req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
1781 proxy.ServeHTTP(rec, req)
1783 assert.Equal(t, http.StatusOK, rec.Code)
1784 assert.Equal(t, "upstream", rec.Body.String())
1786 // checking noCacheHeaders does not exists in response headers from upstream
1787 for k := range noCacheHeaders {
1788 assert.Equal(t, "", rec.Header().Get(k))