@@ -10,6 +10,7 @@ import (
1010 "testing"
1111 "time"
1212
13+ "github.com/alicebob/miniredis"
1314 . "github.com/onsi/ginkgo"
1415 . "github.com/onsi/gomega"
1516 "github.com/pusher/oauth2_proxy/cookie"
@@ -18,6 +19,7 @@ import (
1819 "github.com/pusher/oauth2_proxy/pkg/cookies"
1920 "github.com/pusher/oauth2_proxy/pkg/sessions"
2021 sessionscookie "github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
22+ "github.com/pusher/oauth2_proxy/pkg/sessions/redis"
2123 "github.com/pusher/oauth2_proxy/pkg/sessions/utils"
2224)
2325
@@ -34,6 +36,7 @@ var _ = Describe("NewSessionStore", func() {
3436 var response *httptest.ResponseRecorder
3537 var session *sessionsapi.SessionState
3638 var ss sessionsapi.SessionStore
39+ var mr *miniredis.Miniredis
3740
3841 CheckCookieOptions := func() {
3942 Context("the cookies returned", func() {
@@ -89,19 +92,113 @@ var _ = Describe("NewSessionStore", func() {
8992 })
9093 }
9194
92− SessionStoreInterfaceTests := func() {
93− Context("when Save is called", func() {
95+ // The following should only be for server stores
96+ PersistentSessionStoreTests := func() {
97+ Context("when Clear is called on a persistent store", func() {
98+ var resultCookies []*http.Cookie
99+
94100 BeforeEach(func() {
95− err := ss.Save(response, request, session)
101+ req := httptest.NewRequest("GET", "http://example.com/", nil)
102+ saveResp := httptest.NewRecorder()
103+ err := ss.Save(saveResp, req, session)
104+ Expect(err).ToNot(HaveOccurred())
105+
106+ resultCookies = saveResp.Result().Cookies()
107+ for _, c := range resultCookies {
108+ request.AddCookie(c)
109+ }
110+ err = ss.Clear(response, request)
96111 Expect(err).ToNot(HaveOccurred())
97112 })
98113
99− It("sets a `set-cookie` header in the response", func() {
100− Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty())
114+ Context("attempting to Load", func() {
115+ var loadedAfterClear *sessionsapi.SessionState
116+ var loadErr error
117+
118+ BeforeEach(func() {
119+ loadReq := httptest.NewRequest("GET", "http://example.com/", nil)
120+ for _, c := range resultCookies {
121+ loadReq.AddCookie(c)
122+ }
123+
124+ loadedAfterClear, loadErr = ss.Load(loadReq)
125+ })
126+
127+ It("returns an empty session", func() {
128+ Expect(loadedAfterClear).To(BeNil())
129+ })
130+
131+ It("returns an error", func() {
132+ Expect(loadErr).To(HaveOccurred())
133+ })
101134 })
102135
103− It("Ensures the session CreatedAt is not zero", func() {
104− Expect(session.CreatedAt.IsZero()).To(BeFalse())
136+ CheckCookieOptions()
137+ })
138+ }
139+
140+ SessionStoreInterfaceTests := func(persistent bool) {
141+ Context("when Save is called", func() {
142+ Context("with no existing session", func() {
143+ BeforeEach(func() {
144+ err := ss.Save(response, request, session)
145+ Expect(err).ToNot(HaveOccurred())
146+ })
147+
148+ It("sets a `set-cookie` header in the response", func() {
149+ Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty())
150+ })
151+
152+ It("Ensures the session CreatedAt is not zero", func() {
153+ Expect(session.CreatedAt.IsZero()).To(BeFalse())
154+ })
155+ })
156+
157+ Context("with a broken session", func() {
158+ BeforeEach(func() {
159+ By("Using a valid cookie with a different providers session encoding")
160+ broken := "BrokenSessionFromADifferentSessionImplementation"
161+ value := cookie.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now())
162+ cookie := cookies.MakeCookieFromOptions(request, cookieOpts.CookieName, value, cookieOpts, cookieOpts.CookieExpire, time.Now())
163+ request.AddCookie(cookie)
164+
165+ err := ss.Save(response, request, session)
166+ Expect(err).ToNot(HaveOccurred())
167+ })
168+
169+ It("sets a `set-cookie` header in the response", func() {
170+ Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty())
171+ })
172+
173+ It("Ensures the session CreatedAt is not zero", func() {
174+ Expect(session.CreatedAt.IsZero()).To(BeFalse())
175+ })
176+ })
177+
178+ Context("with an expired saved session", func() {
179+ var err error
180+ BeforeEach(func() {
181+ By("saving a session")
182+ req := httptest.NewRequest("GET", "http://example.com/", nil)
183+ saveResp := httptest.NewRecorder()
184+ err = ss.Save(saveResp, req, session)
185+ Expect(err).ToNot(HaveOccurred())
186+
187+ By("and clearing the session")
188+ for _, c := range saveResp.Result().Cookies() {
189+ request.AddCookie(c)
190+ }
191+ clearResp := httptest.NewRecorder()
192+ err = ss.Clear(clearResp, request)
193+ Expect(err).ToNot(HaveOccurred())
194+
195+ By("then saving a request with the cleared session")
196+ err = ss.Save(response, request, session)
197+ })
198+
199+ It("no error should occur", func() {
200+ Expect(err).ToNot(HaveOccurred())
201+ })
105202 })
106203
107204 CheckCookieOptions()
@@ -109,18 +206,15 @@ var _ = Describe("NewSessionStore", func() {
109206
110207 Context("when Clear is called", func() {
111208 BeforeEach(func() {
112− cookie := cookies.MakeCookie(request,
113− cookieOpts.CookieName,
114− "foo",
115− cookieOpts.CookiePath,
116− cookieOpts.CookieDomain,
117− cookieOpts.CookieHTTPOnly,
118− cookieOpts.CookieSecure,
119− cookieOpts.CookieExpire,
120− time.Now(),
121− )
122− request.AddCookie(cookie)
123− err := ss.Clear(response, request)
209+ req := httptest.NewRequest("GET", "http://example.com/", nil)
210+ saveResp := httptest.NewRecorder()
211+ err := ss.Save(saveResp, req, session)
212+ Expect(err).ToNot(HaveOccurred())
213+
214+ for _, c := range saveResp.Result().Cookies() {
215+ request.AddCookie(c)
216+ }
217+ err = ss.Clear(response, request)
124218 Expect(err).ToNot(HaveOccurred())
125219 })
126220
@@ -132,7 +226,38 @@ var _ = Describe("NewSessionStore", func() {
132226 })
133227
134228 Context("when Load is called", func() {
135− var loadedSession *sessionsapi.SessionState
229+ LoadSessionTests := func() {
230+ var loadedSession *sessionsapi.SessionState
231+ BeforeEach(func() {
232+ var err error
233+ loadedSession, err = ss.Load(request)
234+ Expect(err).ToNot(HaveOccurred())
235+ })
236+
237+ It("loads a session equal to the original session", func() {
238+ if cookieOpts.CookieSecret == "" {
239+ // Only Email and User stored in session when encrypted
240+ Expect(loadedSession.Email).To(Equal(session.Email))
241+ Expect(loadedSession.User).To(Equal(session.User))
242+ } else {
243+ // All fields stored in session if encrypted
244+
245+ // Can't compare time.Time using Equal() so remove ExpiresOn from sessions
246+ l := *loadedSession
247+ l.CreatedAt = time.Time{}
248+ l.ExpiresOn = time.Time{}
249+ s := *session
250+ s.CreatedAt = time.Time{}
251+ s.ExpiresOn = time.Time{}
252+ Expect(l).To(Equal(s))
253+
254+ // Compare time.Time separately
255+ Expect(loadedSession.CreatedAt.Equal(session.CreatedAt)).To(BeTrue())
256+ Expect(loadedSession.ExpiresOn.Equal(session.ExpiresOn)).To(BeTrue())
257+ }
258+ })
259+ }
260+
136261 BeforeEach(func() {
137262 req := httptest.NewRequest("GET", "http://example.com/", nil)
138263 resp := httptest.NewRecorder()
@@ -142,36 +267,57 @@ var _ = Describe("NewSessionStore", func() {
142267 for _, cookie := range resp.Result().Cookies() {
143268 request.AddCookie(cookie)
144269 }
145− loadedSession, err = ss.Load(request)
146− Expect(err).ToNot(HaveOccurred())
147270 })
148271
149− It("loads a session equal to the original session", func() {
150− if cookieOpts.CookieSecret == "" {
151− // Only Email and User stored in session when encrypted
152− Expect(loadedSession.Email).To(Equal(session.Email))
153− Expect(loadedSession.User).To(Equal(session.User))
154− } else {
155− // All fields stored in session if encrypted
156−
157− // Can't compare time.Time using Equal() so remove ExpiresOn from sessions
158− l := *loadedSession
159− l.CreatedAt = time.Time{}
160− l.ExpiresOn = time.Time{}
161− s := *session
162− s.CreatedAt = time.Time{}
163− s.ExpiresOn = time.Time{}
164− Expect(l).To(Equal(s))
165−
166− // Compare time.Time separately
167− Expect(loadedSession.CreatedAt.Equal(session.CreatedAt)).To(BeTrue())
168− Expect(loadedSession.ExpiresOn.Equal(session.ExpiresOn)).To(BeTrue())
169− }
272+ Context("before the refresh period", func() {
273+ LoadSessionTests()
170274 })
275+
276+ // Test TTLs and cleanup of persistent session storage
277+ // For non-persistent we rely on the browser cookie lifecycle
278+ if persistent {
279+ Context("after the refresh period, but before the cookie expire period", func() {
280+ BeforeEach(func() {
281+ switch ss.(type) {
282+ case *redis.SessionStore:
283+ mr.FastForward(cookieOpts.CookieRefresh + time.Minute)
284+ }
285+ })
286+
287+ LoadSessionTests()
288+ })
289+
290+ Context("after the cookie expire period", func() {
291+ var loadedSession *sessionsapi.SessionState
292+ var err error
293+
294+ BeforeEach(func() {
295+ switch ss.(type) {
296+ case *redis.SessionStore:
297+ mr.FastForward(cookieOpts.CookieExpire + time.Minute)
298+ }
299+
300+ loadedSession, err = ss.Load(request)
301+ Expect(err).To(HaveOccurred())
302+ })
303+
304+ It("returns an error loading the session", func() {
305+ Expect(err).To(HaveOccurred())
306+ })
307+
308+ It("returns an empty session", func() {
309+ Expect(loadedSession).To(BeNil())
310+ })
311+ })
312+ }
171313 })
314+
315+ if persistent {
316+ PersistentSessionStoreTests()
317+ }
172318 }
173319
174− RunSessionTests := func() {
320+ RunSessionTests := func(persistent bool) {
175321 Context("with default options", func() {
176322 BeforeEach(func() {
177323 var err error
@@ -179,7 +325,7 @@ var _ = Describe("NewSessionStore", func() {
179325 Expect(err).ToNot(HaveOccurred())
180326 })
181327
182− SessionStoreInterfaceTests()
328+ SessionStoreInterfaceTests(persistent)
183329 })
184330
185331 Context("with non-default options", func() {
@@ -188,7 +334,7 @@ var _ = Describe("NewSessionStore", func() {
188334 CookieName: "_cookie_name",
189335 CookiePath: "/path",
190336 CookieExpire: time.Duration(72) * time.Hour,
191− CookieRefresh: time.Duration(3600),
337+ CookieRefresh: time.Duration(2) * time.Hour,
192338 CookieSecure: false,
193339 CookieHTTPOnly: false,
194340 CookieDomain: "example.com",
@@ -199,7 +345,7 @@ var _ = Describe("NewSessionStore", func() {
199345 Expect(err).ToNot(HaveOccurred())
200346 })
201347
202− SessionStoreInterfaceTests()
348+ SessionStoreInterfaceTests(persistent)
203349 })
204350
205351 Context("with a cipher", func() {
@@ -217,7 +363,7 @@ var _ = Describe("NewSessionStore", func() {
217363 Expect(err).ToNot(HaveOccurred())
218364 })
219365
220− SessionStoreInterfaceTests()
366+ SessionStoreInterfaceTests(persistent)
221367 })
222368 }
223369
@@ -230,7 +376,7 @@ var _ = Describe("NewSessionStore", func() {
230376 CookieName: "_oauth2_proxy",
231377 CookiePath: "/",
232378 CookieExpire: time.Duration(168) * time.Hour,
233− CookieRefresh: time.Duration(0),
379+ CookieRefresh: time.Duration(1) * time.Hour,
234380 CookieSecure: true,
235381 CookieHTTPOnly: true,
236382 }
@@ -260,7 +406,31 @@ var _ = Describe("NewSessionStore", func() {
260406 })
261407
262408 Context("the cookie.SessionStore", func() {
263− RunSessionTests()
409+ RunSessionTests(false)
410+ })
411+ })
412+
413+ Context("with type 'redis'", func() {
414+ BeforeEach(func() {
415+ var err error
416+ mr, err = miniredis.Run()
417+ Expect(err).ToNot(HaveOccurred())
418+ opts.Type = options.RedisSessionStoreType
419+ opts.RedisConnectionURL = "redis://" + mr.Addr()
420+ })
421+
422+ AfterEach(func() {
423+ mr.Close()
424+ })
425+
426+ It("creates a redis.SessionStore", func() {
427+ ss, err := sessions.NewSessionStore(opts, cookieOpts)
428+ Expect(err).NotTo(HaveOccurred())
429+ Expect(ss).To(BeAssignableToTypeOf(&redis.SessionStore{}))
430+ })
431+
432+ Context("the redis.SessionStore", func() {
433+ RunSessionTests(true)
264434 })
265435 })
266436