git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #81 from 18F/access-token-refactor

Refactor pass_access_token changes from #80

commit9534808a0d3ee90d9330e6aa50128d4ba0c344dc authorJehiah Czebotar <jehiah@gmail.com> dateTue 7 Apr 2015, 16:48 (12 years ago) parentb0f040983ad43a
7 files changed+149−51
.gitignore+3 cookies.go+31 cookies_test.go+52 oauthproxy.go+10−35 oauthproxy_test.go+17−16 options.go+17 options_test.go+19
.gitignore+3
@@ -24,3 +24,6 @@ _testmain.go
2424*.exe
2525dist
2626.godeps
27+
28+# Editor swap/temp files
29+.*.swp
cookies.go+31
@@ -97,3 +97,34 @@ func decodeAccessToken(aes_cipher cipher.Block, encoded_access_token string) (st
9797
9898 return string(encrypted_access_token), nil
9999}
100+
101+func buildCookieValue(email string, aes_cipher cipher.Block,
102+ access_token string) (string, error) {
103+ if aes_cipher == nil {
104+ return email, nil
105+ }
106+
107+ encoded_token, err := encodeAccessToken(aes_cipher, access_token)
108+ if err != nil {
109+ return email, fmt.Errorf(
110+ "error encoding access token for %s: %s", email, err)
111+ }
112+ return email + "|" + encoded_token, nil
113+}
114+
115+func parseCookieValue(value string, aes_cipher cipher.Block) (email, user,
116+ access_token string, err error) {
117+ components := strings.Split(value, "|")
118+ email = components[0]
119+ user = strings.Split(email, "@")[0]
120+
121+ if aes_cipher != nil && len(components) == 2 {
122+ access_token, err = decodeAccessToken(aes_cipher, components[1])
123+ if err != nil {
124+ err = fmt.Errorf(
125+ "error decoding access token for %s: %s",
126+ email, err)
127+ }
128+ }
129+ return email, user, access_token, err
130+}
cookies_test.go+52
@@ -3,6 +3,7 @@ package main
33import (
44 "crypto/aes"
55 "github.com/bmizerany/assert"
6+ "strings"
67 "testing"
78)
89
@@ -21,3 +22,54 @@ func TestEncodeAndDecodeAccessToken(t *testing.T) {
2122 assert.NotEqual(t, access_token, encoded_token)
2223 assert.Equal(t, access_token, decoded_token)
2324}
25+
26+func TestBuildCookieValueWithoutAccessToken(t *testing.T) {
27+ value, err := buildCookieValue("michael.bland@gsa.gov", nil, "")
28+ assert.Equal(t, nil, err)
29+ assert.Equal(t, "michael.bland@gsa.gov", value)
30+}
31+
32+func TestBuildCookieValueWithAccessTokenAndNilCipher(t *testing.T) {
33+ value, err := buildCookieValue("michael.bland@gsa.gov", nil,
34+ "access token")
35+ assert.Equal(t, nil, err)
36+ assert.Equal(t, "michael.bland@gsa.gov", value)
37+}
38+
39+func TestParseCookieValueWithoutAccessToken(t *testing.T) {
40+ email, user, access_token, err := parseCookieValue(
41+ "michael.bland@gsa.gov", nil)
42+ assert.Equal(t, nil, err)
43+ assert.Equal(t, "michael.bland@gsa.gov", email)
44+ assert.Equal(t, "michael.bland", user)
45+ assert.Equal(t, "", access_token)
46+}
47+
48+func TestParseCookieValueWithAccessTokenAndNilCipher(t *testing.T) {
49+ email, user, access_token, err := parseCookieValue(
50+ "michael.bland@gsa.gov|access_token", nil)
51+ assert.Equal(t, nil, err)
52+ assert.Equal(t, "michael.bland@gsa.gov", email)
53+ assert.Equal(t, "michael.bland", user)
54+ assert.Equal(t, "", access_token)
55+}
56+
57+func TestBuildAndParseCookieValueWithAccessToken(t *testing.T) {
58+ aes_cipher, err := aes.NewCipher([]byte("0123456789abcdef"))
59+ assert.Equal(t, nil, err)
60+ value, err := buildCookieValue("michael.bland@gsa.gov", aes_cipher,
61+ "access_token")
62+ assert.Equal(t, nil, err)
63+
64+ prefix := "michael.bland@gsa.gov|"
65+ if !strings.HasPrefix(value, prefix) {
66+ t.Fatal("cookie value does not start with \"%s\": %s",
67+ prefix, value)
68+ }
69+
70+ email, user, access_token, err := parseCookieValue(value, aes_cipher)
71+ assert.Equal(t, nil, err)
72+ assert.Equal(t, "michael.bland@gsa.gov", email)
73+ assert.Equal(t, "michael.bland", user)
74+ assert.Equal(t, "access_token", access_token)
75+}
oauthproxy.go+10−35
@@ -47,7 +47,6 @@ type OauthProxy struct {
4747 DisplayHtpasswdForm bool
4848 serveMux http.Handler
4949 PassBasicAuth bool
50− PassAccessToken bool
5150 AesCipher cipher.Block
5251 skipAuthRegex []string
5352 compiledRegex []*regexp.Regexp
@@ -121,20 +120,7 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
121120 log.Printf("Cookie settings: secure (https):%v httponly:%v expiry:%s domain:%s", opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, domain)
122121
123122 var aes_cipher cipher.Block
124−
125− if opts.PassAccessToken == true {
126− valid_cookie_secret_size := false
127− for _, i := range []int{16, 24, 32} {
128− if len(opts.CookieSecret) == i {
129− valid_cookie_secret_size = true
130− }
131− }
132− if valid_cookie_secret_size == false {
133− log.Fatal("cookie_secret must be 16, 24, or 32 bytes " +
134− "to create an AES cipher when " +
135− "pass_access_token == true")
136− }
137−
123+ if opts.PassAccessToken {
138124 var err error
139125 aes_cipher, err = aes.NewCipher([]byte(opts.CookieSecret))
140126 if err != nil {
@@ -163,7 +149,6 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
163149 skipAuthRegex: opts.SkipAuthRegex,
164150 compiledRegex: opts.CompiledRegex,
165151 PassBasicAuth: opts.PassBasicAuth,
166− PassAccessToken: opts.PassAccessToken,
167152 AesCipher: aes_cipher,
168153 templates: loadTemplates(opts.CustomTemplatesDir),
169154 }
@@ -440,20 +425,12 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
440425 // set cookie, or deny
441426 if p.Validator(email) {
442427 log.Printf("%s authenticating %s completed", remoteAddr, email)
443− encoded_token := ""
444− if p.PassAccessToken {
445− encoded_token, err = encodeAccessToken(p.AesCipher, access_token)
446− if err != nil {
447− log.Printf("error encoding access token: %s", err)
448− }
449− }
450− access_token = ""
451−
452− if encoded_token != "" {
453− p.SetCookie(rw, req, email+"|"+encoded_token)
454− } else {
455− p.SetCookie(rw, req, email)
428+ value, err := buildCookieValue(
429+ email, p.AesCipher, access_token)
430+ if err != nil {
431+ log.Printf(err.Error())
456432 }
433+ p.SetCookie(rw, req, value)
457434 http.Redirect(rw, req, redirect, 302)
458435 return
459436 } else {
@@ -467,15 +444,13 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
467444 if err == nil {
468445 var value string
469446 value, ok = validateCookie(cookie, p.CookieSeed)
470− components := strings.Split(value, "|")
471− email = components[0]
472− if len(components) == 2 {
473− access_token, err = decodeAccessToken(p.AesCipher, components[1])
447+ if ok {
448+ email, user, access_token, err = parseCookieValue(
449+ value, p.AesCipher)
474450 if err != nil {
475− log.Printf("error decoding access token: %s", err)
451+ log.Printf(err.Error())
476452 }
477453 }
478− user = strings.Split(email, "@")[0]
479454 }
480455 }
481456
oauthproxy_test.go+17−16
@@ -152,24 +152,25 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
152152 return t
153153}
154154
155−func Close(t *PassAccessTokenTest) {
156− t.provider_server.Close()
155+func (pat_test *PassAccessTokenTest) Close() {
156+ pat_test.provider_server.Close()
157157}
158158
159−func getCallbackEndpoint(pac_test *PassAccessTokenTest) (http_code int, cookie string) {
159+func (pat_test *PassAccessTokenTest) getCallbackEndpoint() (http_code int,
160+ cookie string) {
160161 rw := httptest.NewRecorder()
161162 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code",
162163 strings.NewReader(""))
163164 if err != nil {
164165 return 0, ""
165166 }
166− pac_test.proxy.ServeHTTP(rw, req)
167+ pat_test.proxy.ServeHTTP(rw, req)
167168 return rw.Code, rw.HeaderMap["Set-Cookie"][0]
168169}
169170
170−func getRootEndpoint(pac_test *PassAccessTokenTest, cookie string) (http_code int,
171− access_token string) {
172− cookie_key := pac_test.proxy.CookieKey
171+func (pat_test *PassAccessTokenTest) getRootEndpoint(
172+ cookie string) (http_code int, access_token string) {
173+ cookie_key := pat_test.proxy.CookieKey
173174 var value string
174175 key_prefix := cookie_key + "="
175176
@@ -198,43 +199,43 @@ func getRootEndpoint(pac_test *PassAccessTokenTest, cookie string) (http_code in
198199 })
199200
200201 rw := httptest.NewRecorder()
201− pac_test.proxy.ServeHTTP(rw, req)
202+ pat_test.proxy.ServeHTTP(rw, req)
202203 return rw.Code, rw.Body.String()
203204}
204205
205206func TestForwardAccessTokenUpstream(t *testing.T) {
206− pac_test := NewPassAccessTokenTest(PassAccessTokenTestOptions{
207+ pat_test := NewPassAccessTokenTest(PassAccessTokenTestOptions{
207208 PassAccessToken: true,
208209 })
209− defer Close(pac_test)
210+ defer pat_test.Close()
210211
211212 // A successful validation will redirect and set the auth cookie.
212− code, cookie := getCallbackEndpoint(pac_test)
213+ code, cookie := pat_test.getCallbackEndpoint()
213214 assert.Equal(t, 302, code)
214215 assert.NotEqual(t, nil, cookie)
215216
216217 // Now we make a regular request; the access_token from the cookie is
217218 // forwarded as the "X-Forwarded-Access-Token" header. The token is
218219 // read by the test provider server and written in the response body.
219− code, payload := getRootEndpoint(pac_test, cookie)
220+ code, payload := pat_test.getRootEndpoint(cookie)
220221 assert.Equal(t, 200, code)
221222 assert.Equal(t, "my_auth_token", payload)
222223}
223224
224225func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
225− pac_test := NewPassAccessTokenTest(PassAccessTokenTestOptions{
226+ pat_test := NewPassAccessTokenTest(PassAccessTokenTestOptions{
226227 PassAccessToken: false,
227228 })
228− defer Close(pac_test)
229+ defer pat_test.Close()
229230
230231 // A successful validation will redirect and set the auth cookie.
231− code, cookie := getCallbackEndpoint(pac_test)
232+ code, cookie := pat_test.getCallbackEndpoint()
232233 assert.Equal(t, 302, code)
233234 assert.NotEqual(t, nil, cookie)
234235
235236 // Now we make a regular request, but the access token header should
236237 // not be present.
237− code, payload := getRootEndpoint(pac_test, cookie)
238+ code, payload := pat_test.getRootEndpoint(cookie)
238239 assert.Equal(t, 200, code)
239240 assert.Equal(t, "No access token found.", payload)
240241}
options.go+17
@@ -117,6 +117,23 @@ func (o *Options) Validate() error {
117117 }
118118 msgs = parseProviderInfo(o, msgs)
119119
120+ if o.PassAccessToken {
121+ valid_cookie_secret_size := false
122+ for _, i := range []int{16, 24, 32} {
123+ if len(o.CookieSecret) == i {
124+ valid_cookie_secret_size = true
125+ }
126+ }
127+ if valid_cookie_secret_size == false {
128+ msgs = append(msgs, fmt.Sprintf(
129+ "cookie_secret must be 16, 24, or 32 bytes "+
130+ "to create an AES cipher when "+
131+ "pass_access_token == true, "+
132+ "but is %d bytes",
133+ len(o.CookieSecret)))
134+ }
135+ }
136+
120137 if len(msgs) != 0 {
121138 return fmt.Errorf("Invalid configuration:\n %s",
122139 strings.Join(msgs, "\n "))
options_test.go+19
@@ -102,3 +102,22 @@ func TestDefaultProviderApiSettings(t *testing.T) {
102102 assert.Equal(t, "", p.ProfileUrl.String())
103103 assert.Equal(t, "profile email", p.Scope)
104104}
105+
106+func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) {
107+ o := testOptions()
108+ assert.Equal(t, nil, o.Validate())
109+
110+ assert.Equal(t, false, o.PassAccessToken)
111+ o.PassAccessToken = true
112+ o.CookieSecret = "cookie of invalid length-"
113+ assert.NotEqual(t, nil, o.Validate())
114+
115+ o.CookieSecret = "16 bytes AES-128"
116+ assert.Equal(t, nil, o.Validate())
117+
118+ o.CookieSecret = "24 byte secret AES-192--"
119+ assert.Equal(t, nil, o.Validate())
120+
121+ o.CookieSecret = "32 byte secret for AES-256------"
122+ assert.Equal(t, nil, o.Validate())
123+}