From 15a2cf8b9e174bf4607f089f2a4bced6bad1a3e0 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Tue, 7 May 2019 00:20:36 +0100 Subject: [PATCH] Implement ClearSession for cookie SessionStore --- pkg/cookies/cookies.go | 34 +++++++++++++++++ pkg/sessions/cookie/session_store.go | 56 +++++++++++++++++++++++----- pkg/sessions/session_store_test.go | 22 +++++++++-- 3 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 pkg/cookies/cookies.go diff --git a/pkg/cookies/cookies.go b/pkg/cookies/cookies.go new file mode 100644 index 0000000..936f08e --- /dev/null +++ b/pkg/cookies/cookies.go @@ -0,0 +1,34 @@ +package cookies + +import ( + "net" + "net/http" + "strings" + "time" + + "github.com/pusher/oauth2_proxy/logger" +) + +// MakeCookie constructs a cookie from the given parameters, +// discovering the domain from the request if not specified. +func MakeCookie(req *http.Request, name string, value string, path string, domain string, httpOnly bool, secure bool, expiration time.Duration, now time.Time) *http.Cookie { + if domain != "" { + host := req.Host + if h, _, err := net.SplitHostPort(host); err == nil { + host = h + } + if !strings.HasSuffix(host, domain) { + logger.Printf("Warning: request host is %q but using configured cookie domain of %q", host, domain) + } + } + + return &http.Cookie{ + Name: name, + Value: value, + Path: path, + Domain: domain, + HttpOnly: httpOnly, + Secure: secure, + Expires: now.Add(expiration), + } +} diff --git a/pkg/sessions/cookie/session_store.go b/pkg/sessions/cookie/session_store.go index 2c034b2..36c541e 100644 --- a/pkg/sessions/cookie/session_store.go +++ b/pkg/sessions/cookie/session_store.go @@ -4,12 +4,14 @@ import ( "errors" "fmt" "net/http" + "regexp" "strings" "time" "github.com/pusher/oauth2_proxy/cookie" "github.com/pusher/oauth2_proxy/pkg/apis/options" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/cookies" "github.com/pusher/oauth2_proxy/pkg/sessions/utils" ) @@ -19,10 +21,14 @@ var _ sessions.SessionStore = &SessionStore{} // SessionStore is an implementation of the sessions.SessionStore // interface that stores sessions in client side cookies type SessionStore struct { - CookieCipher *cookie.Cipher - CookieExpire time.Duration - CookieName string - CookieSecret string + CookieCipher *cookie.Cipher + CookieDomain string + CookieExpire time.Duration + CookieHTTPOnly bool + CookieName string + CookiePath string + CookieSecret string + CookieSecure bool } // SaveSession takes a sessions.SessionState and stores the information from it @@ -54,7 +60,35 @@ func (s *SessionStore) LoadSession(req *http.Request) (*sessions.SessionState, e // ClearSession clears any saved session information by writing a cookie to // clear the session func (s *SessionStore) ClearSession(rw http.ResponseWriter, req *http.Request) error { - return fmt.Errorf("method not implemented") + var cookies []*http.Cookie + + // matches CookieName, CookieName_ + var cookieNameRegex = regexp.MustCompile(fmt.Sprintf("^%s(_\\d+)?$", s.CookieName)) + + for _, c := range req.Cookies() { + if cookieNameRegex.MatchString(c.Name) { + clearCookie := s.makeCookie(req, c.Name, "", time.Hour*-1) + + http.SetCookie(rw, clearCookie) + cookies = append(cookies, clearCookie) + } + } + + return nil +} + +func (s *SessionStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration) *http.Cookie { + return cookies.MakeCookie( + req, + name, + value, + s.CookiePath, + s.CookieDomain, + s.CookieHTTPOnly, + s.CookieSecure, + expiration, + time.Now(), + ) } // NewCookieSessionStore initialises a new instance of the SessionStore from @@ -70,10 +104,14 @@ func NewCookieSessionStore(opts options.CookieStoreOptions, cookieOpts *options. } return &SessionStore{ - CookieCipher: cipher, - CookieExpire: cookieOpts.CookieExpire, - CookieName: cookieOpts.CookieName, - CookieSecret: cookieOpts.CookieSecret, + CookieCipher: cipher, + CookieDomain: cookieOpts.CookieDomain, + CookieExpire: cookieOpts.CookieExpire, + CookieHTTPOnly: cookieOpts.CookieHTTPOnly, + CookieName: cookieOpts.CookieName, + CookiePath: cookieOpts.CookiePath, + CookieSecret: cookieOpts.CookieSecret, + CookieSecure: cookieOpts.CookieSecure, }, nil } diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index e841fb9..75ed0ae 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -2,6 +2,7 @@ package sessions_test import ( "net/http" + "net/http/httptest" "testing" "time" @@ -9,6 +10,7 @@ import ( . "github.com/onsi/gomega" "github.com/pusher/oauth2_proxy/pkg/apis/options" sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/cookies" "github.com/pusher/oauth2_proxy/pkg/sessions" "github.com/pusher/oauth2_proxy/pkg/sessions/cookie" ) @@ -23,16 +25,14 @@ var _ = Describe("NewSessionStore", func() { var cookieOpts *options.CookieOptions var request *http.Request - var response http.ResponseWriter + var response *httptest.ResponseRecorder var session *sessionsapi.SessionState CheckCookieOptions := func() { Context("the cookies returned", func() { var cookies []*http.Cookie BeforeEach(func() { - req := http.Request{} - req.Header.Add("Cookie", response.Header().Get("Set-Cookie")) - cookies = req.Cookies() + cookies = response.Result().Cookies() }) It("have the correct name set", func() { @@ -97,6 +97,17 @@ var _ = Describe("NewSessionStore", func() { Context("when ClearSession is called", func() { BeforeEach(func() { + cookie := cookies.MakeCookie(request, + cookieOpts.CookieName, + "foo", + cookieOpts.CookiePath, + cookieOpts.CookieDomain, + cookieOpts.CookieHTTPOnly, + cookieOpts.CookieSecure, + cookieOpts.CookieExpire, + time.Now(), + ) + request.AddCookie(cookie) err := ss.ClearSession(response, request) Expect(err).ToNot(HaveOccurred()) }) @@ -166,6 +177,9 @@ var _ = Describe("NewSessionStore", func() { CookieSecure: true, CookieHTTPOnly: true, } + + request = httptest.NewRequest("GET", "http://example.com/", nil) + response = httptest.NewRecorder() }) Context("with type 'cookie'", func() {