Skip to content
This repository was archived by the owner on Jan 24, 2019. It is now read-only.

Commit e9b5631

Browse files
committed
cookie refresh: validation fixes, interval changes
* refresh now calculated as duration from cookie set
1 parent 66a0484 commit e9b5631

5 files changed

Lines changed: 112 additions & 54 deletions

File tree

contrib/oauth2_proxy.cfg.example

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,17 @@
5454
# custom_templates_dir = ""
5555

5656
## Cookie Settings
57-
## Name - the cookie name
58-
## Secret - the seed string for secure cookies; should be 16, 24, or 32 bytes
59-
## for use with an AES cipher when cookie_refresh or pass_access_token
60-
## is set
61-
## Domain - (optional) cookie domain to force cookies to (ie: .yourcompany.com)
62-
## Expire - (duration) expire timeframe for cookie
63-
## Refresh - (duration) refresh the cookie when less than this much time remains before
64-
## expiration; should be less than cookie_expire; set to 0 to disable.
65-
## Refresh revalidated the OAuth token to ensure it is still valid. ie: 24h
66-
## Secure - secure cookies are only sent by the browser of a HTTPS connection (recommended)
57+
## Name - the cookie name
58+
## Secret - the seed string for secure cookies; should be 16, 24, or 32 bytes
59+
## for use with an AES cipher when cookie_refresh or pass_access_token
60+
## is set
61+
## Domain - (optional) cookie domain to force cookies to (ie: .yourcompany.com)
62+
## Expire - (duration) expire timeframe for cookie
63+
## Refresh - (duration) refresh the cookie when duration has elapsed after cookie was initially set.
64+
## Should be less than cookie_expire; set to 0 to disable.
65+
## On refresh, OAuth token is re-validated.
66+
## (ie: 1h means tokens are refreshed on request 1hr+ after it was set)
67+
## Secure - secure cookies are only sent by the browser of a HTTPS connection (recommended)
6768
## HttpOnly - httponly cookies are not readable by javascript (recommended)
6869
# cookie_name = "_oauth2_proxy"
6970
# cookie_secret = ""

cookies.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,39 @@ import (
1515
"time"
1616
)
1717

18-
func validateCookie(cookie *http.Cookie, seed string) (string, time.Time, bool) {
18+
func validateCookie(cookie *http.Cookie, seed string, expiration time.Duration) (value string, t time.Time, ok bool) {
1919
// value, timestamp, sig
2020
parts := strings.Split(cookie.Value, "|")
2121
if len(parts) != 3 {
22-
return "", time.Unix(0, 0), false
22+
return
2323
}
2424
sig := cookieSignature(seed, cookie.Name, parts[0], parts[1])
2525
if checkHmac(parts[2], sig) {
2626
ts, err := strconv.Atoi(parts[1])
27-
if err == nil && int64(ts) > time.Now().Add(time.Duration(24)*7*time.Hour*-1).Unix() {
27+
if err != nil {
28+
return
29+
}
30+
// The expiration timestamp set when the cookie was created
31+
// isn't sent back by the browser. Hence, we check whether the
32+
// creation timestamp stored in the cookie falls within the
33+
// window defined by (Now()-expiration, Now()].
34+
t = time.Unix(int64(ts), 0)
35+
if t.After(time.Now().Add(expiration*-1)) && t.Before(time.Now().Add(time.Minute*5)) {
2836
// it's a valid cookie. now get the contents
2937
rawValue, err := base64.URLEncoding.DecodeString(parts[0])
3038
if err == nil {
31-
return string(rawValue), time.Unix(int64(ts), 0), true
39+
value = string(rawValue)
40+
ok = true
41+
return
3242
}
3343
}
3444
}
35-
return "", time.Unix(0, 0), false
45+
return
3646
}
3747

38-
func signedCookieValue(seed string, key string, value string) string {
48+
func signedCookieValue(seed string, key string, value string, now time.Time) string {
3949
encodedValue := base64.URLEncoding.EncodeToString([]byte(value))
40-
timeStr := fmt.Sprintf("%d", time.Now().Unix())
50+
timeStr := fmt.Sprintf("%d", now.Unix())
4151
sig := cookieSignature(seed, key, encodedValue, timeStr)
4252
cookieVal := fmt.Sprintf("%s|%s|%s", encodedValue, timeStr, sig)
4353
return cookieVal

oauthproxy.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,12 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
109109
if domain == "" {
110110
domain = "<default>"
111111
}
112+
refresh := "disabled"
113+
if opts.CookieRefresh != time.Duration(0) {
114+
refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
115+
}
112116

113-
log.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s", opts.CookieName, opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, domain)
117+
log.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, domain, refresh)
114118

115119
var aes_cipher cipher.Block
116120
if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) {
@@ -191,7 +195,7 @@ func (p *OauthProxy) redeemCode(host, code string) (string, string, error) {
191195
return access_token, email, nil
192196
}
193197

194-
func (p *OauthProxy) MakeCookie(req *http.Request, value string, expiration time.Duration) *http.Cookie {
198+
func (p *OauthProxy) MakeCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
195199
domain := req.Host
196200
if h, _, err := net.SplitHostPort(domain); err == nil {
197201
domain = h
@@ -204,7 +208,7 @@ func (p *OauthProxy) MakeCookie(req *http.Request, value string, expiration time
204208
}
205209

206210
if value != "" {
207-
value = signedCookieValue(p.CookieSeed, p.CookieName, value)
211+
value = signedCookieValue(p.CookieSeed, p.CookieName, value, now)
208212
}
209213

210214
return &http.Cookie{
@@ -214,36 +218,34 @@ func (p *OauthProxy) MakeCookie(req *http.Request, value string, expiration time
214218
Domain: domain,
215219
HttpOnly: p.CookieHttpOnly,
216220
Secure: p.CookieSecure,
217-
Expires: time.Now().Add(expiration),
221+
Expires: now.Add(expiration),
218222
}
219223
}
220224

221225
func (p *OauthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) {
222-
http.SetCookie(rw, p.MakeCookie(req, "", time.Duration(1)*time.Hour*-1))
226+
http.SetCookie(rw, p.MakeCookie(req, "", time.Hour*-1, time.Now()))
223227
}
224228

225229
func (p *OauthProxy) SetCookie(rw http.ResponseWriter, req *http.Request, val string) {
226-
http.SetCookie(rw, p.MakeCookie(req, val, p.CookieExpire))
230+
http.SetCookie(rw, p.MakeCookie(req, val, p.CookieExpire, time.Now()))
227231
}
228232

229233
func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (email, user, access_token string, ok bool) {
230234
var value string
231235
var timestamp time.Time
232236
cookie, err := req.Cookie(p.CookieName)
233237
if err == nil {
234-
value, timestamp, ok = validateCookie(cookie, p.CookieSeed)
238+
value, timestamp, ok = validateCookie(cookie, p.CookieSeed, p.CookieExpire)
235239
if ok {
236-
email, user, access_token, err = parseCookieValue(
237-
value, p.AesCipher)
240+
email, user, access_token, err = parseCookieValue(value, p.AesCipher)
238241
}
239242
}
240243
if err != nil {
241244
log.Printf(err.Error())
242245
ok = false
243-
} else if p.CookieRefresh != time.Duration(0) {
244-
expires := timestamp.Add(p.CookieExpire)
245-
refresh_threshold := time.Now().Add(p.CookieRefresh)
246-
if refresh_threshold.Unix() > expires.Unix() {
246+
} else if ok && p.CookieRefresh != time.Duration(0) {
247+
refresh := timestamp.Add(p.CookieRefresh)
248+
if refresh.Before(time.Now()) {
247249
ok = p.Validator(email) && p.provider.ValidateToken(access_token)
248250
if ok {
249251
p.SetCookie(rw, req, value)

oauthproxy_test.go

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -348,13 +348,12 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts) *ProcessCookieTest {
348348

349349
pc_test.opts = NewOptions()
350350
pc_test.opts.Upstreams = append(pc_test.opts.Upstreams, "unused")
351-
pc_test.opts.CookieSecret = "foobar"
352351
pc_test.opts.ClientID = "bazquux"
353352
pc_test.opts.ClientSecret = "xyzzyplugh"
354353
pc_test.opts.CookieSecret = "0123456789abcdef"
355354
// First, set the CookieRefresh option so proxy.AesCipher is created,
356355
// needed to encrypt the access_token.
357-
pc_test.opts.CookieRefresh = time.Duration(24) * time.Hour
356+
pc_test.opts.CookieRefresh = time.Hour
358357
pc_test.opts.Validate()
359358

360359
pc_test.proxy = NewOauthProxy(pc_test.opts, func(email string) bool {
@@ -379,14 +378,13 @@ func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
379378
})
380379
}
381380

382-
func (p *ProcessCookieTest) MakeCookie(value, access_token string) *http.Cookie {
383-
cookie_value, _ := buildCookieValue(
384-
value, p.proxy.AesCipher, access_token)
385-
return p.proxy.MakeCookie(p.req, cookie_value, p.opts.CookieExpire)
381+
func (p *ProcessCookieTest) MakeCookie(value, access_token string, ref time.Time) *http.Cookie {
382+
cookie_value, _ := buildCookieValue(value, p.proxy.AesCipher, access_token)
383+
return p.proxy.MakeCookie(p.req, cookie_value, p.opts.CookieExpire, ref)
386384
}
387385

388386
func (p *ProcessCookieTest) AddCookie(value, access_token string) {
389-
p.req.AddCookie(p.MakeCookie(value, access_token))
387+
p.req.AddCookie(p.MakeCookie(value, access_token, time.Now()))
390388
}
391389

392390
func (p *ProcessCookieTest) ProcessCookie() (email, user, access_token string, ok bool) {
@@ -416,15 +414,16 @@ func TestProcessCookieFailIfParsingCookieValueFails(t *testing.T) {
416414
pc_test.proxy.AesCipher, "my_access_token")
417415
pc_test.req.AddCookie(pc_test.proxy.MakeCookie(
418416
pc_test.req, value+"some bogus bytes",
419-
pc_test.opts.CookieExpire))
417+
pc_test.opts.CookieExpire, time.Now()))
420418
_, _, _, ok := pc_test.ProcessCookie()
421419
assert.Equal(t, false, ok)
422420
}
423421

424422
func TestProcessCookieRefreshNotSet(t *testing.T) {
425423
pc_test := NewProcessCookieTestWithDefaults()
426424
pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour
427-
cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "")
425+
reference := time.Now().Add(time.Duration(-2) * time.Hour)
426+
cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "", reference)
428427
pc_test.req.AddCookie(cookie)
429428

430429
_, _, _, ok := pc_test.ProcessCookie()
@@ -435,36 +434,70 @@ func TestProcessCookieRefreshNotSet(t *testing.T) {
435434
func TestProcessCookieRefresh(t *testing.T) {
436435
pc_test := NewProcessCookieTestWithDefaults()
437436
pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour
438-
cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token")
437+
reference := time.Now().Add(time.Duration(-2) * time.Hour)
438+
cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token", reference)
439439
pc_test.req.AddCookie(cookie)
440440

441-
pc_test.proxy.CookieRefresh = time.Duration(24) * time.Hour
441+
pc_test.proxy.CookieRefresh = time.Hour
442442
_, _, _, ok := pc_test.ProcessCookie()
443443
assert.Equal(t, true, ok)
444444
assert.NotEqual(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"])
445445
}
446446

447447
func TestProcessCookieRefreshThresholdNotCrossed(t *testing.T) {
448448
pc_test := NewProcessCookieTestWithDefaults()
449-
pc_test.proxy.CookieExpire = time.Duration(25) * time.Hour
450-
cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token")
449+
pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour
450+
reference := time.Now().Add(time.Duration(-30) * time.Minute)
451+
cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token", reference)
451452
pc_test.req.AddCookie(cookie)
452453

453-
pc_test.proxy.CookieRefresh = time.Duration(24) * time.Hour
454+
pc_test.proxy.CookieRefresh = time.Hour
454455
_, _, _, ok := pc_test.ProcessCookie()
455456
assert.Equal(t, true, ok)
456457
assert.Equal(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"])
457458
}
458459

460+
func TestProcessCookieFailIfCookieExpired(t *testing.T) {
461+
pc_test := NewProcessCookieTestWithDefaults()
462+
pc_test.proxy.CookieExpire = time.Duration(24) * time.Hour
463+
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
464+
cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token", reference)
465+
pc_test.req.AddCookie(cookie)
466+
467+
if _, _, _, ok := pc_test.ProcessCookie(); ok {
468+
t.Error("ProcessCookie() should have failed")
469+
}
470+
if set_cookie := pc_test.rw.HeaderMap["Set-Cookie"]; set_cookie != nil {
471+
t.Error("expected Set-Cookie to be nil, instead was: ", set_cookie)
472+
}
473+
}
474+
475+
func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
476+
pc_test := NewProcessCookieTestWithDefaults()
477+
pc_test.proxy.CookieExpire = time.Duration(24) * time.Hour
478+
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
479+
cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token", reference)
480+
pc_test.req.AddCookie(cookie)
481+
482+
pc_test.proxy.CookieRefresh = time.Hour
483+
if _, _, _, ok := pc_test.ProcessCookie(); ok {
484+
t.Error("ProcessCookie() should have failed")
485+
}
486+
if set_cookie := pc_test.rw.HeaderMap["Set-Cookie"]; set_cookie != nil {
487+
t.Error("expected Set-Cookie to be nil, instead was: ", set_cookie)
488+
}
489+
}
490+
459491
func TestProcessCookieFailIfRefreshSetAndTokenNoLongerValid(t *testing.T) {
460492
pc_test := NewProcessCookieTest(ProcessCookieTestOpts{
461493
provider_validate_cookie_response: false,
462494
})
463495
pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour
464-
cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token")
496+
reference := time.Now().Add(time.Duration(-24) * time.Hour)
497+
cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token", reference)
465498
pc_test.req.AddCookie(cookie)
466499

467-
pc_test.proxy.CookieRefresh = time.Duration(24) * time.Hour
500+
pc_test.proxy.CookieRefresh = time.Hour
468501
_, _, _, ok := pc_test.ProcessCookie()
469502
assert.Equal(t, false, ok)
470503
assert.Equal(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"])
@@ -475,10 +508,11 @@ func TestProcessCookieFailIfRefreshSetAndUserNoLongerValid(t *testing.T) {
475508
pc_test.validate_user = false
476509

477510
pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour
478-
cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token")
511+
reference := time.Now().Add(time.Duration(-2) * time.Hour)
512+
cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token", reference)
479513
pc_test.req.AddCookie(cookie)
480514

481-
pc_test.proxy.CookieRefresh = time.Duration(24) * time.Hour
515+
pc_test.proxy.CookieRefresh = time.Hour
482516
_, _, _, ok := pc_test.ProcessCookie()
483517
assert.Equal(t, false, ok)
484518
assert.Equal(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"])

providers/internal_util.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,34 @@
11
package providers
22

33
import (
4-
"github.com/bitly/oauth2_proxy/api"
4+
"io/ioutil"
55
"log"
66
"net/http"
7+
"net/url"
8+
9+
"github.com/bitly/oauth2_proxy/api"
710
)
811

912
func validateToken(p Provider, access_token string, header http.Header) bool {
1013
if access_token == "" || p.Data().ValidateUrl == nil {
1114
return false
1215
}
13-
url := p.Data().ValidateUrl.String()
16+
endpoint := p.Data().ValidateUrl.String()
1417
if len(header) == 0 {
15-
url = url + "?access_token=" + access_token
18+
params := url.Values{"access_token": {access_token}}
19+
endpoint = endpoint + "?" + params.Encode()
1620
}
17-
if resp, err := api.RequestUnparsedResponse(url, header); err != nil {
21+
resp, err := api.RequestUnparsedResponse(endpoint, header)
22+
if err != nil {
1823
log.Printf("token validation request failed: %s", err)
1924
return false
20-
} else {
21-
return resp.StatusCode == 200
2225
}
26+
27+
body, _ := ioutil.ReadAll(resp.Body)
28+
resp.Body.Close()
29+
if resp.StatusCode == 200 {
30+
return true
31+
}
32+
log.Printf("token validation request failed: status %d - %s", resp.StatusCode, body)
33+
return false
2334
}

0 commit comments

Comments
 (0)