6 "github.com/18F/hmacauth"
7 "github.com/bitly/oauth2_proxy/providers"
8 "github.com/bmizerany/assert"
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) {
30 hostname, _, _ := net.SplitHostPort(r.Host)
31 w.Write([]byte(hostname))
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)
53func TestEncodedSlashes(t *testing.T) {
55 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
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)
72 t.Fatalf("err %s", err)
74 if seen != encodedPath {
75 t.Errorf("got bad request %q expected %q", seen, encodedPath)
79func TestRobotsTxt(t *testing.T) {
81 opts.ClientID = "bazquux"
82 opts.ClientSecret = "foobar"
83 opts.CookieSecret = "xyzzyplugh"
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
100func NewTestProvider(provider_url *url.URL, email_address string) *TestProvider {
101 return &TestProvider{
102 ProviderData: &providers.ProviderData{
103 ProviderName: "Test Provider",
106 Host: provider_url.Host,
107 Path: "/oauth/authorize",
111 Host: provider_url.Host,
112 Path: "/oauth/token",
114 ProfileURL: &url.URL{
116 Host: provider_url.Host,
117 Path: "/api/v1/profile",
119 Scope: "profile.email",
121 EmailAddress: email_address,
125func (tp *TestProvider) GetEmailAddress(session *providers.SessionState) (string, error) {
126 return tp.EmailAddress, nil
129func (tp *TestProvider) ValidateSessionState(session *providers.SessionState) bool {
133func TestBasicAuthPassword(t *testing.T) {
134 provider_server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
140 payload = `{"access_token": "my_auth_token"}`
142 payload = r.Header.Get("Authorization")
144 payload = "No Authorization header found."
148 w.Write([]byte(payload))
151 opts.Upstreams = append(opts.Upstreams, provider_server.URL)
152 // The CookieSecret must be 32 bytes in order to create the AES
154 opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
155 opts.ClientID = "bazquux"
156 opts.ClientSecret = "foobar"
157 opts.CookieSecure = false
158 opts.PassBasicAuth = true
159 opts.BasicAuthPassword = "This is a secure password"
162 provider_url, _ := url.Parse(provider_server.URL)
163 const email_address = "michael.bland@gsa.gov"
164 const user_name = "michael.bland"
166 opts.provider = NewTestProvider(provider_url, email_address)
167 proxy := NewOAuthProxy(opts, func(email string) bool {
168 return email == email_address
171 rw := httptest.NewRecorder()
172 req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code",
173 strings.NewReader(""))
174 proxy.ServeHTTP(rw, req)
175 cookie := rw.HeaderMap["Set-Cookie"][0]
177 cookieName := proxy.CookieName
179 key_prefix := cookieName + "="
181 for _, field := range strings.Split(cookie, "; ") {
182 value = strings.TrimPrefix(field, key_prefix)
190 req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
191 req.AddCookie(&http.Cookie{
195 Expires: time.Now().Add(time.Duration(24)),
199 rw = httptest.NewRecorder()
200 proxy.ServeHTTP(rw, req)
201 expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(user_name+":"+opts.BasicAuthPassword))
202 assert.Equal(t, expectedHeader, rw.Body.String())
203 provider_server.Close()
206type PassAccessTokenTest struct {
207 provider_server *httptest.Server
212type PassAccessTokenTestOptions struct {
216func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
217 t := &PassAccessTokenTest{}
219 t.provider_server = httptest.NewServer(
220 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
226 payload = `{"access_token": "my_auth_token"}`
228 payload = r.Header.Get("X-Forwarded-Access-Token")
230 payload = "No access token found."
234 w.Write([]byte(payload))
237 t.opts = NewOptions()
238 t.opts.Upstreams = append(t.opts.Upstreams, t.provider_server.URL)
239 // The CookieSecret must be 32 bytes in order to create the AES
241 t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
242 t.opts.ClientID = "bazquux"
243 t.opts.ClientSecret = "foobar"
244 t.opts.CookieSecure = false
245 t.opts.PassAccessToken = opts.PassAccessToken
248 provider_url, _ := url.Parse(t.provider_server.URL)
249 const email_address = "michael.bland@gsa.gov"
251 t.opts.provider = NewTestProvider(provider_url, email_address)
252 t.proxy = NewOAuthProxy(t.opts, func(email string) bool {
253 return email == email_address
258func (pat_test *PassAccessTokenTest) Close() {
259 pat_test.provider_server.Close()
262func (pat_test *PassAccessTokenTest) getCallbackEndpoint() (http_code int,
264 rw := httptest.NewRecorder()
265 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code",
266 strings.NewReader(""))
270 pat_test.proxy.ServeHTTP(rw, req)
271 return rw.Code, rw.HeaderMap["Set-Cookie"][0]
274func (pat_test *PassAccessTokenTest) getRootEndpoint(cookie string) (http_code int, access_token string) {
275 cookieName := pat_test.proxy.CookieName
277 key_prefix := cookieName + "="
279 for _, field := range strings.Split(cookie, "; ") {
280 value = strings.TrimPrefix(field, key_prefix)
291 req, err := http.NewRequest("GET", "/", strings.NewReader(""))
295 req.AddCookie(&http.Cookie{
299 Expires: time.Now().Add(time.Duration(24)),
303 rw := httptest.NewRecorder()
304 pat_test.proxy.ServeHTTP(rw, req)
305 return rw.Code, rw.Body.String()
308func TestForwardAccessTokenUpstream(t *testing.T) {
309 pat_test := NewPassAccessTokenTest(PassAccessTokenTestOptions{
310 PassAccessToken: true,
312 defer pat_test.Close()
314 // A successful validation will redirect and set the auth cookie.
315 code, cookie := pat_test.getCallbackEndpoint()
316 assert.Equal(t, 302, code)
317 assert.NotEqual(t, nil, cookie)
319 // Now we make a regular request; the access_token from the cookie is
320 // forwarded as the "X-Forwarded-Access-Token" header. The token is
321 // read by the test provider server and written in the response body.
322 code, payload := pat_test.getRootEndpoint(cookie)
323 assert.Equal(t, 200, code)
324 assert.Equal(t, "my_auth_token", payload)
327func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
328 pat_test := NewPassAccessTokenTest(PassAccessTokenTestOptions{
329 PassAccessToken: false,
331 defer pat_test.Close()
333 // A successful validation will redirect and set the auth cookie.
334 code, cookie := pat_test.getCallbackEndpoint()
335 assert.Equal(t, 302, code)
336 assert.NotEqual(t, nil, cookie)
338 // Now we make a regular request, but the access token header should
340 code, payload := pat_test.getRootEndpoint(cookie)
341 assert.Equal(t, 200, code)
342 assert.Equal(t, "No access token found.", payload)
345type SignInPageTest struct {
348 sign_in_regexp *regexp.Regexp
351const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
353func NewSignInPageTest() *SignInPageTest {
354 var sip_test SignInPageTest
356 sip_test.opts = NewOptions()
357 sip_test.opts.CookieSecret = "foobar"
358 sip_test.opts.ClientID = "bazquux"
359 sip_test.opts.ClientSecret = "xyzzyplugh"
360 sip_test.opts.Validate()
362 sip_test.proxy = NewOAuthProxy(sip_test.opts, func(email string) bool {
365 sip_test.sign_in_regexp = regexp.MustCompile(signInRedirectPattern)
370func (sip_test *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
371 rw := httptest.NewRecorder()
372 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
373 sip_test.proxy.ServeHTTP(rw, req)
374 return rw.Code, rw.Body.String()
377func TestSignInPageIncludesTargetRedirect(t *testing.T) {
378 sip_test := NewSignInPageTest()
379 const endpoint = "/some/random/endpoint"
381 code, body := sip_test.GetEndpoint(endpoint)
382 assert.Equal(t, 403, code)
384 match := sip_test.sign_in_regexp.FindStringSubmatch(body)
386 t.Fatal("Did not find pattern in body: " +
387 signInRedirectPattern + "\nBody:\n" + body)
389 if match[1] != endpoint {
390 t.Fatal(`expected redirect to "` + endpoint +
391 `", but was "` + match[1] + `"`)
395func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
396 sip_test := NewSignInPageTest()
397 code, body := sip_test.GetEndpoint("/oauth2/sign_in")
398 assert.Equal(t, 200, code)
400 match := sip_test.sign_in_regexp.FindStringSubmatch(body)
402 t.Fatal("Did not find pattern in body: " +
403 signInRedirectPattern + "\nBody:\n" + body)
406 t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`)
410type ProcessCookieTest struct {
413 rw *httptest.ResponseRecorder
415 provider TestProvider
420type ProcessCookieTestOpts struct {
421 provider_validate_cookie_response bool
424func NewProcessCookieTest(opts ProcessCookieTestOpts) *ProcessCookieTest {
425 var pc_test ProcessCookieTest
427 pc_test.opts = NewOptions()
428 pc_test.opts.ClientID = "bazquux"
429 pc_test.opts.ClientSecret = "xyzzyplugh"
430 pc_test.opts.CookieSecret = "0123456789abcdefabcd"
431 // First, set the CookieRefresh option so proxy.AesCipher is created,
432 // needed to encrypt the access_token.
433 pc_test.opts.CookieRefresh = time.Hour
434 pc_test.opts.Validate()
436 pc_test.proxy = NewOAuthProxy(pc_test.opts, func(email string) bool {
437 return pc_test.validate_user
439 pc_test.proxy.provider = &TestProvider{
440 ValidToken: opts.provider_validate_cookie_response,
443 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
444 // access_token validation.
445 pc_test.proxy.CookieRefresh = time.Duration(0)
446 pc_test.rw = httptest.NewRecorder()
447 pc_test.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
448 pc_test.validate_user = true
452func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
453 return NewProcessCookieTest(ProcessCookieTestOpts{
454 provider_validate_cookie_response: true,
458func (p *ProcessCookieTest) MakeCookie(value string, ref time.Time) *http.Cookie {
459 return p.proxy.MakeCookie(p.req, value, p.opts.CookieExpire, ref)
462func (p *ProcessCookieTest) SaveSession(s *providers.SessionState, ref time.Time) error {
463 value, err := p.proxy.provider.CookieForSession(s, p.proxy.CookieCipher)
467 p.req.AddCookie(p.proxy.MakeCookie(p.req, value, p.proxy.CookieExpire, ref))
471func (p *ProcessCookieTest) LoadCookiedSession() (*providers.SessionState, time.Duration, error) {
472 return p.proxy.LoadCookiedSession(p.req)
475func TestLoadCookiedSession(t *testing.T) {
476 pc_test := NewProcessCookieTestWithDefaults()
478 startSession := &providers.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
479 pc_test.SaveSession(startSession, time.Now())
481 session, _, err := pc_test.LoadCookiedSession()
482 assert.Equal(t, nil, err)
483 assert.Equal(t, startSession.Email, session.Email)
484 assert.Equal(t, "michael.bland", session.User)
485 assert.Equal(t, startSession.AccessToken, session.AccessToken)
488func TestProcessCookieNoCookieError(t *testing.T) {
489 pc_test := NewProcessCookieTestWithDefaults()
491 session, _, err := pc_test.LoadCookiedSession()
492 assert.Equal(t, "Cookie \"_oauth2_proxy\" not present", err.Error())
494 t.Errorf("expected nil session. got %#v", session)
498func TestProcessCookieRefreshNotSet(t *testing.T) {
499 pc_test := NewProcessCookieTestWithDefaults()
500 pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour
501 reference := time.Now().Add(time.Duration(-2) * time.Hour)
503 startSession := &providers.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
504 pc_test.SaveSession(startSession, reference)
506 session, age, err := pc_test.LoadCookiedSession()
507 assert.Equal(t, nil, err)
508 if age < time.Duration(-2)*time.Hour {
509 t.Errorf("cookie too young %v", age)
511 assert.Equal(t, startSession.Email, session.Email)
514func TestProcessCookieFailIfCookieExpired(t *testing.T) {
515 pc_test := NewProcessCookieTestWithDefaults()
516 pc_test.proxy.CookieExpire = time.Duration(24) * time.Hour
517 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
518 startSession := &providers.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
519 pc_test.SaveSession(startSession, reference)
521 session, _, err := pc_test.LoadCookiedSession()
522 assert.NotEqual(t, nil, err)
524 t.Errorf("expected nil session %#v", session)
528func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
529 pc_test := NewProcessCookieTestWithDefaults()
530 pc_test.proxy.CookieExpire = time.Duration(24) * time.Hour
531 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
532 startSession := &providers.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
533 pc_test.SaveSession(startSession, reference)
535 pc_test.proxy.CookieRefresh = time.Hour
536 session, _, err := pc_test.LoadCookiedSession()
537 assert.NotEqual(t, nil, err)
539 t.Errorf("expected nil session %#v", session)
543func NewAuthOnlyEndpointTest() *ProcessCookieTest {
544 pc_test := NewProcessCookieTestWithDefaults()
545 pc_test.req, _ = http.NewRequest("GET",
546 pc_test.opts.ProxyPrefix+"/auth", nil)
550func TestAuthOnlyEndpointAccepted(t *testing.T) {
551 test := NewAuthOnlyEndpointTest()
552 startSession := &providers.SessionState{
553 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
554 test.SaveSession(startSession, time.Now())
556 test.proxy.ServeHTTP(test.rw, test.req)
557 assert.Equal(t, http.StatusAccepted, test.rw.Code)
558 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
559 assert.Equal(t, "", string(bodyBytes))
562func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
563 test := NewAuthOnlyEndpointTest()
565 test.proxy.ServeHTTP(test.rw, test.req)
566 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
567 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
568 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
571func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
572 test := NewAuthOnlyEndpointTest()
573 test.proxy.CookieExpire = time.Duration(24) * time.Hour
574 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
575 startSession := &providers.SessionState{
576 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
577 test.SaveSession(startSession, reference)
579 test.proxy.ServeHTTP(test.rw, test.req)
580 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
581 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
582 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
585func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
586 test := NewAuthOnlyEndpointTest()
587 startSession := &providers.SessionState{
588 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
589 test.SaveSession(startSession, time.Now())
590 test.validate_user = false
592 test.proxy.ServeHTTP(test.rw, test.req)
593 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
594 bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
595 assert.Equal(t, "unauthorized request\n", string(bodyBytes))
598type SignatureAuthenticator struct {
599 auth hmacauth.HmacAuth
602func (v *SignatureAuthenticator) Authenticate(
603 w http.ResponseWriter, r *http.Request) {
604 result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
605 if result == hmacauth.ResultNoSignature {
606 w.Write([]byte("no signature received"))
607 } else if result == hmacauth.ResultMatch {
608 w.Write([]byte("signatures match"))
609 } else if result == hmacauth.ResultMismatch {
610 w.Write([]byte("signatures do not match:" +
611 "\n received: " + headerSig +
612 "\n computed: " + computedSig))
614 panic("Unknown result value: " + result.String())
618type SignatureTest struct {
620 upstream *httptest.Server
622 provider *httptest.Server
624 rw *httptest.ResponseRecorder
625 authenticator *SignatureAuthenticator
628func NewSignatureTest() *SignatureTest {
630 opts.CookieSecret = "cookie secret"
631 opts.ClientID = "client ID"
632 opts.ClientSecret = "client secret"
633 opts.EmailDomains = []string{"acm.org"}
635 authenticator := &SignatureAuthenticator{}
636 upstream := httptest.NewServer(
637 http.HandlerFunc(authenticator.Authenticate))
638 upstream_url, _ := url.Parse(upstream.URL)
639 opts.Upstreams = append(opts.Upstreams, upstream.URL)
641 providerHandler := func(w http.ResponseWriter, r *http.Request) {
642 w.Write([]byte(`{"access_token": "my_auth_token"}`))
644 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
645 provider_url, _ := url.Parse(provider.URL)
646 opts.provider = NewTestProvider(provider_url, "mbland@acm.org")
648 return &SignatureTest{
654 httptest.NewRecorder(),
659func (st *SignatureTest) Close() {
664// fakeNetConn simulates an http.Request.Body buffer that will be consumed
665// when it is read by the hmacauth.HmacAuth if not handled properly. See:
666// https://github.com/18F/hmacauth/pull/4
667type fakeNetConn struct {
671func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
672 if bodyLen := len(fnc.reqBody); bodyLen != 0 {
675 return bodyLen, io.EOF
680func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
681 err := st.opts.Validate()
685 proxy := NewOAuthProxy(st.opts, func(email string) bool { return true })
687 var bodyBuf io.ReadCloser
689 bodyBuf = ioutil.NopCloser(&fakeNetConn{reqBody: body})
691 req, err := http.NewRequest(method, "/foo/bar", bodyBuf)
695 req.Header = st.header
697 state := &providers.SessionState{
698 Email: "mbland@acm.org", AccessToken: "my_access_token"}
699 value, err := proxy.provider.CookieForSession(state, proxy.CookieCipher)
703 cookie := proxy.MakeCookie(req, value, proxy.CookieExpire, time.Now())
704 req.AddCookie(cookie)
705 // This is used by the upstream to validate the signature.
706 st.authenticator.auth = hmacauth.NewHmacAuth(
707 crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
708 proxy.ServeHTTP(st.rw, req)
711func TestNoRequestSignature(t *testing.T) {
712 st := NewSignatureTest()
714 st.MakeRequestWithExpectedKey("GET", "", "")
715 assert.Equal(t, 200, st.rw.Code)
716 assert.Equal(t, st.rw.Body.String(), "no signature received")
719func TestRequestSignatureGetRequest(t *testing.T) {
720 st := NewSignatureTest()
722 st.opts.SignatureKey = "sha1:foobar"
723 st.MakeRequestWithExpectedKey("GET", "", "foobar")
724 assert.Equal(t, 200, st.rw.Code)
725 assert.Equal(t, st.rw.Body.String(), "signatures match")
728func TestRequestSignaturePostRequest(t *testing.T) {
729 st := NewSignatureTest()
731 st.opts.SignatureKey = "sha1:foobar"
732 payload := `{ "hello": "world!" }`
733 st.MakeRequestWithExpectedKey("POST", payload, "foobar")
734 assert.Equal(t, 200, st.rw.Code)
735 assert.Equal(t, st.rw.Body.String(), "signatures match")
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list