From 8e2d83600cfa6682b8969fda0c5e526d8c26d6d8 Mon Sep 17 00:00:00 2001 From: Mike Bland Date: Fri, 8 May 2015 10:00:57 -0400 Subject: [PATCH] Implement cookie auto-refresh The intention is to refresh the cookie whenever the user accesses an authenticated service with less than `cookie-refresh` time to go before the cookie expires. --- cookies.go | 8 ++++---- main.go | 1 + oauthproxy.go | 12 ++++++++++-- oauthproxy_test.go | 25 ++++++++++++++++++++++++- options.go | 2 ++ 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/cookies.go b/cookies.go index 9605398..8d7ce63 100644 --- a/cookies.go +++ b/cookies.go @@ -15,11 +15,11 @@ import ( "time" ) -func validateCookie(cookie *http.Cookie, seed string) (string, bool) { +func validateCookie(cookie *http.Cookie, seed string) (string, time.Time, bool) { // value, timestamp, sig parts := strings.Split(cookie.Value, "|") if len(parts) != 3 { - return "", false + return "", time.Unix(0, 0), false } sig := cookieSignature(seed, cookie.Name, parts[0], parts[1]) if checkHmac(parts[2], sig) { @@ -28,11 +28,11 @@ func validateCookie(cookie *http.Cookie, seed string) (string, bool) { // it's a valid cookie. now get the contents rawValue, err := base64.URLEncoding.DecodeString(parts[0]) if err == nil { - return string(rawValue), true + return string(rawValue), time.Unix(int64(ts), 0), true } } } - return "", false + return "", time.Unix(0, 0), false } func signedCookieValue(seed string, key string, value string) string { diff --git a/main.go b/main.go index d6c99da..1b86c1e 100644 --- a/main.go +++ b/main.go @@ -45,6 +45,7 @@ func main() { flagSet.String("cookie-secret", "", "the seed string for secure cookies") flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*") flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie") + flagSet.Duration("cookie-refresh", time.Duration(24)*time.Hour, "refresh the cookie when this much time remains before expiration") flagSet.Bool("cookie-https-only", true, "set secure (HTTPS) cookies (deprecated. use --cookie-secure setting)") flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag") flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag") diff --git a/oauthproxy.go b/oauthproxy.go index f28928e..c236cd3 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -33,6 +33,7 @@ type OauthProxy struct { CookieSecure bool CookieHttpOnly bool CookieExpire time.Duration + CookieRefresh time.Duration Validator func(string) bool redirectUrl *url.URL // the url to receive requests at @@ -136,6 +137,7 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy { CookieSecure: opts.CookieSecure, CookieHttpOnly: opts.CookieHttpOnly, CookieExpire: opts.CookieExpire, + CookieRefresh: opts.CookieRefresh, Validator: validator, clientID: opts.ClientID, @@ -259,10 +261,11 @@ func (p *OauthProxy) SetCookie(rw http.ResponseWriter, req *http.Request, val st } func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (email, user, access_token string, ok bool) { + var value string + var timestamp time.Time cookie, err := req.Cookie(p.CookieKey) if err == nil { - var value string - value, ok = validateCookie(cookie, p.CookieSeed) + value, timestamp, ok = validateCookie(cookie, p.CookieSeed) if ok { email, user, access_token, err = parseCookieValue( value, p.AesCipher) @@ -270,6 +273,11 @@ func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (e } if err != nil { log.Printf(err.Error()) + } else if p.CookieRefresh != time.Duration(0) { + refresh_threshold := time.Now().Add(p.CookieRefresh) + if refresh_threshold.Unix() > timestamp.Unix() { + p.SetCookie(rw, req, value) + } } return } diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 0e436c7..c38c31e 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -353,8 +353,31 @@ func TestProcessCookie(t *testing.T) { assert.Equal(t, "michael.bland", user) } -func TestProcessCookieError(t *testing.T) { +func TestProcessCookieNoCookieError(t *testing.T) { pc_test := NewProcessCookieTest() _, _, _, ok := pc_test.ProcessCookie() assert.Equal(t, false, ok) } + +func TestProcessCookieRefreshNotSet(t *testing.T) { + pc_test := NewProcessCookieTest() + cookie := pc_test.MakeCookie("michael.bland@gsa.gov") + cookie.Expires = time.Now().Add(time.Duration(23) * time.Hour) + pc_test.req.AddCookie(cookie) + + _, _, _, ok := pc_test.ProcessCookie() + assert.Equal(t, true, ok) + assert.Equal(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"]) +} + +func TestProcessCookieRefresh(t *testing.T) { + pc_test := NewProcessCookieTest() + cookie := pc_test.MakeCookie("michael.bland@gsa.gov") + cookie.Expires = time.Now().Add(time.Duration(23) * time.Hour) + pc_test.req.AddCookie(cookie) + + pc_test.proxy.CookieRefresh = time.Duration(24) * time.Hour + _, _, _, ok := pc_test.ProcessCookie() + assert.Equal(t, true, ok) + assert.NotEqual(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"]) +} diff --git a/options.go b/options.go index bbfd466..f9147d9 100644 --- a/options.go +++ b/options.go @@ -26,6 +26,7 @@ type Options struct { CookieSecret string `flag:"cookie-secret" cfg:"cookie_secret" env:"GOOGLE_AUTH_PROXY_COOKIE_SECRET"` CookieDomain string `flag:"cookie-domain" cfg:"cookie_domain" env:"GOOGLE_AUTH_PROXY_COOKIE_DOMAIN"` CookieExpire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"GOOGLE_AUTH_PROXY_COOKIE_EXPIRE"` + CookieRefresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"GOOGLE_AUTH_PROXY_COOKIE_REFRESH"` CookieHttpsOnly bool `flag:"cookie-https-only" cfg:"cookie_https_only"` // deprecated use cookie-secure CookieSecure bool `flag:"cookie-secure" cfg:"cookie_secure"` CookieHttpOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly"` @@ -61,6 +62,7 @@ func NewOptions() *Options { CookieSecure: true, CookieHttpOnly: true, CookieExpire: time.Duration(168) * time.Hour, + CookieRefresh: time.Duration(0), PassBasicAuth: true, PassAccessToken: false, PassHostHeader: true,