git.iko.soy/typeable/oauth2-proxy
on v2.2 help sign in
typeable/oauth2-proxy/oauthproxy_test.go
805 lines 24 kB history raw
1package main
3import (
4 "crypto"
5 "encoding/base64"
6 "github.com/18F/hmacauth"
7 "github.com/bitly/oauth2_proxy/providers"
8 "github.com/bmizerany/assert"
9 "io"
10 "io/ioutil"
11 "log"
12 "net"
13 "net/http"
14 "net/http/httptest"
15 "net/url"
16 "regexp"
17 "strings"
18 "testing"
19 "time"
22func init() {
23 log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
27func TestNewReverseProxy(t *testing.T) {
28 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
29 w.WriteHeader(200)
30 hostname, _, _ := net.SplitHostPort(r.Host)
31 w.Write([]byte(hostname))
32 }))
33 defer backend.Close()
35 backendURL, _ := url.Parse(backend.URL)
36 backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host)
37 backendHost := net.JoinHostPort(backendHostname, backendPort)
38 proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
40 proxyHandler := NewReverseProxy(proxyURL)
41 setProxyUpstreamHostHeader(proxyHandler, proxyURL)
42 frontend := httptest.NewServer(proxyHandler)
43 defer frontend.Close()
45 getReq, _ := http.NewRequest("GET", frontend.URL, nil)
46 res, _ := http.DefaultClient.Do(getReq)
47 bodyBytes, _ := ioutil.ReadAll(res.Body)
48 if g, e := string(bodyBytes), backendHostname; g != e {
49 t.Errorf("got body %q; expected %q", g, e)
50 }
53func TestEncodedSlashes(t *testing.T) {
54 var seen string
55 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56 w.WriteHeader(200)
57 seen = r.RequestURI
58 }))
59 defer backend.Close()
61 b, _ := url.Parse(backend.URL)
62 proxyHandler := NewReverseProxy(b)
63 setProxyDirector(proxyHandler)
64 frontend := httptest.NewServer(proxyHandler)
65 defer frontend.Close()
67 f, _ := url.Parse(frontend.URL)
68 encodedPath := "/a%2Fb/?c=1"
69 getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: f.Host, Opaque: encodedPath}}
70 _, err := http.DefaultClient.Do(getReq)
71 if err != nil {
72 t.Fatalf("err %s", err)
73 }
74 if seen != encodedPath {
75 t.Errorf("got bad request %q expected %q", seen, encodedPath)
76 }
79func TestRobotsTxt(t *testing.T) {
80 opts := NewOptions()
81 opts.ClientID = "bazquux"
82 opts.ClientSecret = "foobar"
83 opts.CookieSecret = "xyzzyplugh"
84 opts.Validate()
86 proxy := NewOAuthProxy(opts, func(string) bool { return true })
87 rw := httptest.NewRecorder()
88 req, _ := http.NewRequest("GET", "/robots.txt", nil)
89 proxy.ServeHTTP(rw, req)
90 assert.Equal(t, 200, rw.Code)
91 assert.Equal(t, "User-agent: *\nDisallow: /", rw.Body.String())
94type TestProvider struct {
95 *providers.ProviderData
96 EmailAddress string
97 ValidToken bool
100func NewTestProvider(provider_url *url.URL, email_address string) *TestProvider {
101 return &TestProvider{
102 ProviderData: &providers.ProviderData{
103 ProviderName: "Test Provider",
104 LoginURL: &url.URL{
105 Scheme: "http",
106 Host: provider_url.Host,
107 Path: "/oauth/authorize",
108 },
109 RedeemURL: &url.URL{
110 Scheme: "http",
111 Host: provider_url.Host,
112 Path: "/oauth/token",
113 },
114 ProfileURL: &url.URL{
115 Scheme: "http",
116 Host: provider_url.Host,
117 Path: "/api/v1/profile",
118 },
119 Scope: "profile.email",
120 },
121 EmailAddress: email_address,
122 }
125func (tp *TestProvider) GetEmailAddress(session *providers.SessionState) (string, error) {
126 return tp.EmailAddress, nil
129func (tp *TestProvider) ValidateSessionState(session *providers.SessionState) bool {
130 return tp.ValidToken
133func TestBasicAuthPassword(t *testing.T) {
134 provider_server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
135 log.Printf("%#v", r)
136 url := r.URL
137 payload := ""
138 switch url.Path {
139 case "/oauth/token":
140 payload = `{"access_token": "my_auth_token"}`
141 default:
142 payload = r.Header.Get("Authorization")
143 if payload == "" {
144 payload = "No Authorization header found."
145 }
146 }
147 w.WriteHeader(200)
148 w.Write([]byte(payload))
149 }))
150 opts := NewOptions()
151 opts.Upstreams = append(opts.Upstreams, provider_server.URL)
152 // The CookieSecret must be 32 bytes in order to create the AES
153 // cipher.
154 opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
155 opts.ClientID = "bazquux"
156 opts.ClientSecret = "foobar"
157 opts.CookieSecure = false
158 opts.PassBasicAuth = true
159 opts.PassUserHeaders = true
160 opts.BasicAuthPassword = "This is a secure password"
161 opts.Validate()
163 provider_url, _ := url.Parse(provider_server.URL)
164 const email_address = "michael.bland@gsa.gov"
165 const user_name = "michael.bland"
167 opts.provider = NewTestProvider(provider_url, email_address)
168 proxy := NewOAuthProxy(opts, func(email string) bool {
169 return email == email_address
170 })
172 rw := httptest.NewRecorder()
173 req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
174 strings.NewReader(""))
175 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
176 proxy.ServeHTTP(rw, req)
177 if rw.Code >= 400 {
178 t.Fatalf("expected 3xx got %d", rw.Code)
179 }
180 cookie := rw.HeaderMap["Set-Cookie"][1]
182 cookieName := proxy.CookieName
183 var value string
184 key_prefix := cookieName + "="
186 for _, field := range strings.Split(cookie, "; ") {
187 value = strings.TrimPrefix(field, key_prefix)
188 if value != field {
189 break
190 } else {
191 value = ""
192 }
193 }
195 req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
196 req.AddCookie(&http.Cookie{
197 Name: cookieName,
198 Value: value,
199 Path: "/",
200 Expires: time.Now().Add(time.Duration(24)),
201 HttpOnly: true,
202 })
203 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
205 rw = httptest.NewRecorder()
206 proxy.ServeHTTP(rw, req)
208 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(user_name+":"+opts.BasicAuthPassword))
209 assert.Equal(t, expectedHeader, rw.Body.String())
210 provider_server.Close()
213type PassAccessTokenTest struct {
214 provider_server *httptest.Server
215 proxy *OAuthProxy
216 opts *Options
219type PassAccessTokenTestOptions struct {
220 PassAccessToken bool
223func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
224 t := &PassAccessTokenTest{}
226 t.provider_server = httptest.NewServer(
227 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
228 log.Printf("%#v", r)
229 url := r.URL
230 payload := ""
231 switch url.Path {
232 case "/oauth/token":
233 payload = `{"access_token": "my_auth_token"}`
234 default:
235 payload = r.Header.Get("X-Forwarded-Access-Token")
236 if payload == "" {
237 payload = "No access token found."
238 }
239 }
240 w.WriteHeader(200)
241 w.Write([]byte(payload))
242 }))
244 t.opts = NewOptions()
245 t.opts.Upstreams = append(t.opts.Upstreams, t.provider_server.URL)
246 // The CookieSecret must be 32 bytes in order to create the AES
247 // cipher.
248 t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
249 t.opts.ClientID = "bazquux"
250 t.opts.ClientSecret = "foobar"
251 t.opts.CookieSecure = false
252 t.opts.PassAccessToken = opts.PassAccessToken
253 t.opts.Validate()
255 provider_url, _ := url.Parse(t.provider_server.URL)
256 const email_address = "michael.bland@gsa.gov"
258 t.opts.provider = NewTestProvider(provider_url, email_address)
259 t.proxy = NewOAuthProxy(t.opts, func(email string) bool {
260 return email == email_address
261 })
262 return t
265func (pat_test *PassAccessTokenTest) Close() {
266 pat_test.provider_server.Close()
269func (pat_test *PassAccessTokenTest) getCallbackEndpoint() (http_code int,
270 cookie string) {
271 rw := httptest.NewRecorder()
272 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
273 strings.NewReader(""))
274 if err != nil {
275 return 0, ""
276 }
277 req.AddCookie(pat_test.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
278 pat_test.proxy.ServeHTTP(rw, req)
279 return rw.Code, rw.HeaderMap["Set-Cookie"][1]
282func (pat_test *PassAccessTokenTest) getRootEndpoint(cookie string) (http_code int, access_token string) {
283 cookieName := pat_test.proxy.CookieName
284 var value string
285 key_prefix := cookieName + "="
287 for _, field := range strings.Split(cookie, "; ") {
288 value = strings.TrimPrefix(field, key_prefix)
289 if value != field {
290 break
291 } else {
292 value = ""
293 }
294 }
295 if value == "" {
296 return 0, ""
297 }
299 req, err := http.NewRequest("GET", "/", strings.NewReader(""))
300 if err != nil {
301 return 0, ""
302 }
303 req.AddCookie(&http.Cookie{
304 Name: cookieName,
305 Value: value,
306 Path: "/",
307 Expires: time.Now().Add(time.Duration(24)),
308 HttpOnly: true,
309 })
311 rw := httptest.NewRecorder()
312 pat_test.proxy.ServeHTTP(rw, req)
313 return rw.Code, rw.Body.String()
316func TestForwardAccessTokenUpstream(t *testing.T) {
317 pat_test := NewPassAccessTokenTest(PassAccessTokenTestOptions{
318 PassAccessToken: true,
319 })
320 defer pat_test.Close()
322 // A successful validation will redirect and set the auth cookie.
323 code, cookie := pat_test.getCallbackEndpoint()
324 if code != 302 {
325 t.Fatalf("expected 302; got %d", code)
326 }
327 assert.NotEqual(t, nil, cookie)
329 // Now we make a regular request; the access_token from the cookie is
330 // forwarded as the "X-Forwarded-Access-Token" header. The token is
331 // read by the test provider server and written in the response body.
332 code, payload := pat_test.getRootEndpoint(cookie)
333 if code != 200 {
334 t.Fatalf("expected 200; got %d", code)
335 }
336 assert.Equal(t, "my_auth_token", payload)
339func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
340 pat_test := NewPassAccessTokenTest(PassAccessTokenTestOptions{
341 PassAccessToken: false,
342 })
343 defer pat_test.Close()
345 // A successful validation will redirect and set the auth cookie.
346 code, cookie := pat_test.getCallbackEndpoint()
347 if code != 302 {
348 t.Fatalf("expected 302; got %d", code)
349 }
350 assert.NotEqual(t, nil, cookie)
352 // Now we make a regular request, but the access token header should
353 // not be present.
354 code, payload := pat_test.getRootEndpoint(cookie)
355 if code != 200 {
356 t.Fatalf("expected 200; got %d", code)
357 }
358 assert.Equal(t, "No access token found.", payload)
361type SignInPageTest struct {
362 opts *Options
363 proxy *OAuthProxy
364 sign_in_regexp *regexp.Regexp
367const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
369func NewSignInPageTest() *SignInPageTest {
370 var sip_test SignInPageTest
372 sip_test.opts = NewOptions()
373 sip_test.opts.CookieSecret = "foobar"
374 sip_test.opts.ClientID = "bazquux"
375 sip_test.opts.ClientSecret = "xyzzyplugh"
376 sip_test.opts.Validate()
378 sip_test.proxy = NewOAuthProxy(sip_test.opts, func(email string) bool {
379 return true
380 })
381 sip_test.sign_in_regexp = regexp.MustCompile(signInRedirectPattern)
383 return &sip_test
386func (sip_test *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
387 rw := httptest.NewRecorder()
388 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
389 sip_test.proxy.ServeHTTP(rw, req)
390 return rw.Code, rw.Body.String()
393func TestSignInPageIncludesTargetRedirect(t *testing.T) {
394 sip_test := NewSignInPageTest()
395 const endpoint = "/some/random/endpoint"
397 code, body := sip_test.GetEndpoint(endpoint)
398 assert.Equal(t, 403, code)
400 match := sip_test.sign_in_regexp.FindStringSubmatch(body)
401 if match == nil {
402 t.Fatal("Did not find pattern in body: " +
403 signInRedirectPattern + "\nBody:\n" + body)
404 }
405 if match[1] != endpoint {
406 t.Fatal(`expected redirect to "` + endpoint +
407 `", but was "` + match[1] + `"`)
408 }
411func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
412 sip_test := NewSignInPageTest()
413 code, body := sip_test.GetEndpoint("/oauth2/sign_in")
414 assert.Equal(t, 200, code)
416 match := sip_test.sign_in_regexp.FindStringSubmatch(body)
417 if match == nil {
418 t.Fatal("Did not find pattern in body: " +
419 signInRedirectPattern + "\nBody:\n" + body)
420 }
421 if match[1] != "/" {
422 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
423 }
426type ProcessCookieTest struct {
427 opts *Options
428 proxy *OAuthProxy
429 rw *httptest.ResponseRecorder
430 req *http.Request
431 provider TestProvider
432 response_code int
433 validate_user bool
436type ProcessCookieTestOpts struct {
437 provider_validate_cookie_response bool
440func NewProcessCookieTest(opts ProcessCookieTestOpts) *ProcessCookieTest {
441 var pc_test ProcessCookieTest
443 pc_test.opts = NewOptions()
444 pc_test.opts.ClientID = "bazquux"
445 pc_test.opts.ClientSecret = "xyzzyplugh"
446 pc_test.opts.CookieSecret = "0123456789abcdefabcd"
447 // First, set the CookieRefresh option so proxy.AesCipher is created,
448 // needed to encrypt the access_token.
449 pc_test.opts.CookieRefresh = time.Hour
450 pc_test.opts.Validate()
452 pc_test.proxy = NewOAuthProxy(pc_test.opts, func(email string) bool {
453 return pc_test.validate_user
454 })
455 pc_test.proxy.provider = &TestProvider{
456 ValidToken: opts.provider_validate_cookie_response,
457 }
459 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
460 // access_token validation.
461 pc_test.proxy.CookieRefresh = time.Duration(0)
462 pc_test.rw = httptest.NewRecorder()
463 pc_test.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
464 pc_test.validate_user = true
465 return &pc_test
468func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
469 return NewProcessCookieTest(ProcessCookieTestOpts{
470 provider_validate_cookie_response: true,
471 })
474func (p *ProcessCookieTest) MakeCookie(value string, ref time.Time) *http.Cookie {
475 return p.proxy.MakeSessionCookie(p.req, value, p.opts.CookieExpire, ref)
478func (p *ProcessCookieTest) SaveSession(s *providers.SessionState, ref time.Time) error {
479 value, err := p.proxy.provider.CookieForSession(s, p.proxy.CookieCipher)
480 if err != nil {
481 return err
482 }
483 p.req.AddCookie(p.proxy.MakeSessionCookie(p.req, value, p.proxy.CookieExpire, ref))
484 return nil
487func (p *ProcessCookieTest) LoadCookiedSession() (*providers.SessionState, time.Duration, error) {
488 return p.proxy.LoadCookiedSession(p.req)
491func TestLoadCookiedSession(t *testing.T) {
492 pc_test := NewProcessCookieTestWithDefaults()
494 startSession := &providers.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
495 pc_test.SaveSession(startSession, time.Now())
497 session, _, err := pc_test.LoadCookiedSession()
498 assert.Equal(t, nil, err)
499 assert.Equal(t, startSession.Email, session.Email)
500 assert.Equal(t, "michael.bland", session.User)
501 assert.Equal(t, startSession.AccessToken, session.AccessToken)
504func TestProcessCookieNoCookieError(t *testing.T) {
505 pc_test := NewProcessCookieTestWithDefaults()
507 session, _, err := pc_test.LoadCookiedSession()
508 assert.Equal(t, "Cookie \"_oauth2_proxy\" not present", err.Error())
509 if session != nil {
510 t.Errorf("expected nil session. got %#v", session)
511 }
514func TestProcessCookieRefreshNotSet(t *testing.T) {
515 pc_test := NewProcessCookieTestWithDefaults()
516 pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour
517 reference := time.Now().Add(time.Duration(-2) * time.Hour)
519 startSession := &providers.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
520 pc_test.SaveSession(startSession, reference)
522 session, age, err := pc_test.LoadCookiedSession()
523 assert.Equal(t, nil, err)
524 if age < time.Duration(-2)*time.Hour {
525 t.Errorf("cookie too young %v", age)
526 }
527 assert.Equal(t, startSession.Email, session.Email)
530func TestProcessCookieFailIfCookieExpired(t *testing.T) {
531 pc_test := NewProcessCookieTestWithDefaults()
532 pc_test.proxy.CookieExpire = time.Duration(24) * time.Hour
533 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
534 startSession := &providers.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
535 pc_test.SaveSession(startSession, reference)
537 session, _, err := pc_test.LoadCookiedSession()
538 assert.NotEqual(t, nil, err)
539 if session != nil {
540 t.Errorf("expected nil session %#v", session)
541 }
544func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
545 pc_test := NewProcessCookieTestWithDefaults()
546 pc_test.proxy.CookieExpire = time.Duration(24) * time.Hour
547 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
548 startSession := &providers.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
549 pc_test.SaveSession(startSession, reference)
551 pc_test.proxy.CookieRefresh = time.Hour
552 session, _, err := pc_test.LoadCookiedSession()
553 assert.NotEqual(t, nil, err)
554 if session != nil {
555 t.Errorf("expected nil session %#v", session)
556 }
559func NewAuthOnlyEndpointTest() *ProcessCookieTest {
560 pc_test := NewProcessCookieTestWithDefaults()
561 pc_test.req, _ = http.NewRequest("GET",
562 pc_test.opts.ProxyPrefix+"/auth", nil)
563 return pc_test
566func TestAuthOnlyEndpointAccepted(t *testing.T) {
567 test := NewAuthOnlyEndpointTest()
568 startSession := &providers.SessionState{
569 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
570 test.SaveSession(startSession, time.Now())
572 test.proxy.ServeHTTP(test.rw, test.req)
573 assert.Equal(t, http.StatusAccepted, test.rw.Code)
574 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
575 assert.Equal(t, "", string(bodyBytes))
578func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
579 test := NewAuthOnlyEndpointTest()
581 test.proxy.ServeHTTP(test.rw, test.req)
582 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
583 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
584 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
587func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
588 test := NewAuthOnlyEndpointTest()
589 test.proxy.CookieExpire = time.Duration(24) * time.Hour
590 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
591 startSession := &providers.SessionState{
592 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
593 test.SaveSession(startSession, reference)
595 test.proxy.ServeHTTP(test.rw, test.req)
596 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
597 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
598 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
601func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
602 test := NewAuthOnlyEndpointTest()
603 startSession := &providers.SessionState{
604 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
605 test.SaveSession(startSession, time.Now())
606 test.validate_user = false
608 test.proxy.ServeHTTP(test.rw, test.req)
609 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
610 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
611 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
614func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
615 var pc_test ProcessCookieTest
617 pc_test.opts = NewOptions()
618 pc_test.opts.SetXAuthRequest = true
619 pc_test.opts.Validate()
621 pc_test.proxy = NewOAuthProxy(pc_test.opts, func(email string) bool {
622 return pc_test.validate_user
623 })
624 pc_test.proxy.provider = &TestProvider{
625 ValidToken: true,
626 }
628 pc_test.validate_user = true
630 pc_test.rw = httptest.NewRecorder()
631 pc_test.req, _ = http.NewRequest("GET",
632 pc_test.opts.ProxyPrefix+"/auth", nil)
634 startSession := &providers.SessionState{
635 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token"}
636 pc_test.SaveSession(startSession, time.Now())
638 pc_test.proxy.ServeHTTP(pc_test.rw, pc_test.req)
639 assert.Equal(t, http.StatusAccepted, pc_test.rw.Code)
640 assert.Equal(t, "oauth_user", pc_test.rw.HeaderMap["X-Auth-Request-User"][0])
641 assert.Equal(t, "oauth_user@example.com", pc_test.rw.HeaderMap["X-Auth-Request-Email"][0])
644func TestAuthSkippedForPreflightRequests(t *testing.T) {
645 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
646 w.WriteHeader(200)
647 w.Write([]byte("response"))
648 }))
649 defer upstream.Close()
651 opts := NewOptions()
652 opts.Upstreams = append(opts.Upstreams, upstream.URL)
653 opts.ClientID = "bazquux"
654 opts.ClientSecret = "foobar"
655 opts.CookieSecret = "xyzzyplugh"
656 opts.SkipAuthPreflight = true
657 opts.Validate()
659 upstream_url, _ := url.Parse(upstream.URL)
660 opts.provider = NewTestProvider(upstream_url, "")
662 proxy := NewOAuthProxy(opts, func(string) bool { return false })
663 rw := httptest.NewRecorder()
664 req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
665 proxy.ServeHTTP(rw, req)
667 assert.Equal(t, 200, rw.Code)
668 assert.Equal(t, "response", rw.Body.String())
671type SignatureAuthenticator struct {
672 auth hmacauth.HmacAuth
675func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Request) {
676 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
677 if result == hmacauth.ResultNoSignature {
678 w.Write([]byte("no signature received"))
679 } else if result == hmacauth.ResultMatch {
680 w.Write([]byte("signatures match"))
681 } else if result == hmacauth.ResultMismatch {
682 w.Write([]byte("signatures do not match:" +
683 "\n received: " + headerSig +
684 "\n computed: " + computedSig))
685 } else {
686 panic("Unknown result value: " + result.String())
687 }
690type SignatureTest struct {
691 opts *Options
692 upstream *httptest.Server
693 upstream_host string
694 provider *httptest.Server
695 header http.Header
696 rw *httptest.ResponseRecorder
697 authenticator *SignatureAuthenticator
700func NewSignatureTest() *SignatureTest {
701 opts := NewOptions()
702 opts.CookieSecret = "cookie secret"
703 opts.ClientID = "client ID"
704 opts.ClientSecret = "client secret"
705 opts.EmailDomains = []string{"acm.org"}
707 authenticator := &SignatureAuthenticator{}
708 upstream := httptest.NewServer(
709 http.HandlerFunc(authenticator.Authenticate))
710 upstream_url, _ := url.Parse(upstream.URL)
711 opts.Upstreams = append(opts.Upstreams, upstream.URL)
713 providerHandler := func(w http.ResponseWriter, r *http.Request) {
714 w.Write([]byte(`{"access_token": "my_auth_token"}`))
715 }
716 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
717 provider_url, _ := url.Parse(provider.URL)
718 opts.provider = NewTestProvider(provider_url, "mbland@acm.org")
720 return &SignatureTest{
721 opts,
722 upstream,
723 upstream_url.Host,
724 provider,
725 make(http.Header),
726 httptest.NewRecorder(),
727 authenticator,
728 }
731func (st *SignatureTest) Close() {
732 st.provider.Close()
733 st.upstream.Close()
736// fakeNetConn simulates an http.Request.Body buffer that will be consumed
737// when it is read by the hmacauth.HmacAuth if not handled properly. See:
738// https://github.com/18F/hmacauth/pull/4
739type fakeNetConn struct {
740 reqBody string
743func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
744 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
745 copy(p, fnc.reqBody)
746 fnc.reqBody = ""
747 return bodyLen, io.EOF
748 }
749 return 0, io.EOF
752func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
753 err := st.opts.Validate()
754 if err != nil {
755 panic(err)
756 }
757 proxy := NewOAuthProxy(st.opts, func(email string) bool { return true })
759 var bodyBuf io.ReadCloser
760 if body != "" {
761 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
762 }
763 req := httptest.NewRequest(method, "/foo/bar", bodyBuf)
764 req.Header = st.header
766 state := &providers.SessionState{
767 Email: "mbland@acm.org", AccessToken: "my_access_token"}
768 value, err := proxy.provider.CookieForSession(state, proxy.CookieCipher)
769 if err != nil {
770 panic(err)
771 }
772 cookie := proxy.MakeSessionCookie(req, value, proxy.CookieExpire, time.Now())
773 req.AddCookie(cookie)
774 // This is used by the upstream to validate the signature.
775 st.authenticator.auth = hmacauth.NewHmacAuth(
776 crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
777 proxy.ServeHTTP(st.rw, req)
780func TestNoRequestSignature(t *testing.T) {
781 st := NewSignatureTest()
782 defer st.Close()
783 st.MakeRequestWithExpectedKey("GET", "", "")
784 assert.Equal(t, 200, st.rw.Code)
785 assert.Equal(t, st.rw.Body.String(), "no signature received")
788func TestRequestSignatureGetRequest(t *testing.T) {
789 st := NewSignatureTest()
790 defer st.Close()
791 st.opts.SignatureKey = "sha1:foobar"
792 st.MakeRequestWithExpectedKey("GET", "", "foobar")
793 assert.Equal(t, 200, st.rw.Code)
794 assert.Equal(t, st.rw.Body.String(), "signatures match")
797func TestRequestSignaturePostRequest(t *testing.T) {
798 st := NewSignatureTest()
799 defer st.Close()
800 st.opts.SignatureKey = "sha1:foobar"
801 payload := `{ "hello": "world!" }`
802 st.MakeRequestWithExpectedKey("POST", payload, "foobar")
803 assert.Equal(t, 200, st.rw.Code)
804 assert.Equal(t, st.rw.Body.String(), "signatures match")