git.iko.soy/typeable/oauth2-proxy
on master help sign in
test: replace mock pkg/clock with narrowly targeted stub clocks. (#3238)

The package under pkg/clock is github.com/benbjohnson/clock, which is archived. It's also way more complex than is what is actually needed here, so we can entirely remove the dependency and remove the helper package. Fixes #2840. Signed-off-by: David Symonds <dsymonds@gmail.com>

commit110d51d1d742b5cde9cb629d6912f47e5d9ab878 authorDavid Symonds <dsymonds@gmail.com> dateTue 28 Oct 2025, 20:05 (1 year ago) parent8f687e4
13 files changed+50−594
CHANGELOG.md+1 go.mod−1 go.sum−2 pkg/apis/sessions/session_state.go+12−6 pkg/apis/sessions/session_state_test.go+3−3 pkg/clock/clock.go−157 pkg/clock/clock_suite_test.go−17 pkg/clock/clock_test.go−380 pkg/cookies/csrf.go+8−8 pkg/cookies/csrf_per_request_test.go+5−5 pkg/cookies/csrf_test.go+3−3 pkg/middleware/stored_session_test.go+15−10 providers/provider_default_test.go+3−2
CHANGELOG.md+1
@@ -10,6 +10,7 @@
1010
1111- [#3228](https://github.com/oauth2-proxy/oauth2-proxy/pull/3228) fix: use GetSecret() in ticket.go makeCookie to respect cookie-secret-file (@stagswtf)
1212- [#3244](https://github.com/oauth2-proxy/oauth2-proxy/pull/3244) chore(deps): upgrade to latest go1.25.3 (@tuunit)
13+- [#3238](https://github.com/oauth2-proxy/oauth2-proxy/pull/3238) chore: Replace pkg/clock with narrowly targeted stub clocks (@dsymonds)
1314
1415# V7.12.0
1516
go.mod−1
@@ -7,7 +7,6 @@ require (
77 github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
88 github.com/a8m/envsubst v1.4.3
99 github.com/alicebob/miniredis/v2 v2.35.0
10− github.com/benbjohnson/clock v1.3.5
1110 github.com/bitly/go-simplejson v0.5.1
1211 github.com/bsm/redislock v0.9.4
1312 github.com/coreos/go-oidc/v3 v3.14.1
go.sum−2
@@ -14,8 +14,6 @@ github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGn
1414github.com/alicebob/miniredis/v2 v2.11.1/go.mod h1:UA48pmi7aSazcGAvcdKcBB49z521IC9VjTTRz2nIaJE=
1515github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
1616github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
17−github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
18−github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
1917github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
2018github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
2119github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
pkg/apis/sessions/session_state.go+12−6
@@ -7,7 +7,6 @@ import (
77 "io"
88 "time"
99
10− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/clock"
1110 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
1211 "github.com/pierrec/lz4/v4"
1312 "github.com/vmihailenco/msgpack/v5"
@@ -30,8 +29,15 @@ type SessionState struct {
3029 PreferredUsername string `msgpack:"pu,omitempty"`
3130
3231 // Internal helpers, not serialized
33− Clock clock.Clock `msgpack:"-"`
34− Lock Lock `msgpack:"-"`
32+ Clock func() time.Time `msgpack:"-"` // override for time.Now, for testing
33+ Lock Lock `msgpack:"-"`
34+}
35+
36+func (s *SessionState) now() time.Time {
37+ if s.Clock != nil {
38+ return s.Clock()
39+ }
40+ return time.Now()
3541}
3642
3743func (s *SessionState) ObtainLock(ctx context.Context, expiration time.Duration) error {
@@ -64,7 +70,7 @@ func (s *SessionState) PeekLock(ctx context.Context) (bool, error) {
6470
6571// CreatedAtNow sets a SessionState's CreatedAt to now
6672func (s *SessionState) CreatedAtNow() {
67− now := s.Clock.Now()
73+ now := s.now()
6874 s.CreatedAt = &now
6975}
7076
@@ -85,7 +91,7 @@ func (s *SessionState) ExpiresIn(d time.Duration) {
8591
8692// IsExpired checks whether the session has expired
8793func (s *SessionState) IsExpired() bool {
88− if s.ExpiresOn != nil && !s.ExpiresOn.IsZero() && s.ExpiresOn.Before(s.Clock.Now()) {
94+ if s.ExpiresOn != nil && !s.ExpiresOn.IsZero() && s.ExpiresOn.Before(s.now()) {
8995 return true
9096 }
9197 return false
@@ -94,7 +100,7 @@ func (s *SessionState) IsExpired() bool {
94100// Age returns the age of a session
95101func (s *SessionState) Age() time.Duration {
96102 if s.CreatedAt != nil && !s.CreatedAt.IsZero() {
97− return s.Clock.Now().Truncate(time.Second).Sub(*s.CreatedAt)
103+ return s.now().Truncate(time.Second).Sub(*s.CreatedAt)
98104 }
99105 return 0
100106}
pkg/apis/sessions/session_state_test.go+3−3
@@ -22,7 +22,7 @@ func TestCreatedAtNow(t *testing.T) {
2222 ss := &SessionState{}
2323
2424 now := time.Unix(1234567890, 0)
25− ss.Clock.Set(now)
25+ ss.Clock = func() time.Time { return now }
2626
2727 ss.CreatedAtNow()
2828 g.Expect(*ss.CreatedAt).To(Equal(now))
@@ -33,9 +33,9 @@ func TestExpiresIn(t *testing.T) {
3333 ss := &SessionState{}
3434
3535 now := time.Unix(1234567890, 0)
36− ss.Clock.Set(now)
36+ ss.Clock = func() time.Time { return now }
3737
38− ttl := time.Duration(743) * time.Second
38+ ttl := 743 * time.Second
3939 ss.ExpiresIn(ttl)
4040
4141 g.Expect(*ss.ExpiresOn).To(Equal(ss.CreatedAt.Add(ttl)))
pkg/clock/clock.godeleted−157
@@ -1,157 +0,0 @@
1−package clock
2−
3−import (
4− "errors"
5− "sync"
6− "time"
7−
8− clockapi "github.com/benbjohnson/clock"
9−)
10−
11−var (
12− globalClock = clockapi.New()
13− mu sync.Mutex
14−)
15−
16−// Set the global clock to a clockapi.Mock with the given time.Time
17−func Set(t time.Time) {
18− mu.Lock()
19− defer mu.Unlock()
20− mock, ok := globalClock.(*clockapi.Mock)
21− if !ok {
22− mock = clockapi.NewMock()
23− }
24− mock.Set(t)
25− globalClock = mock
26−}
27−
28−// Add moves the mocked global clock forward the given duration. It will error
29−// if the global clock is not mocked.
30−func Add(d time.Duration) error {
31− mu.Lock()
32− defer mu.Unlock()
33− mock, ok := globalClock.(*clockapi.Mock)
34− if !ok {
35− return errors.New("time not mocked")
36− }
37− mock.Add(d)
38− return nil
39−}
40−
41−// Reset sets the global clock to a pure time implementation. Returns any
42−// existing Mock if set in case lingering time operations are attached to it.
43−func Reset() *clockapi.Mock {
44− mu.Lock()
45− defer mu.Unlock()
46− existing := globalClock
47− globalClock = clockapi.New()
48−
49− mock, ok := existing.(*clockapi.Mock)
50− if !ok {
51− return nil
52− }
53− return mock
54−}
55−
56−// Clock is a non-package level wrapper around time that supports stubbing.
57−// It will use its localized stubs (allowing for parallelized unit tests
58−// where package level stubbing would cause issues). It falls back to any
59−// package level time stubs for non-parallel, cross-package integration
60−// testing scenarios.
61−//
62−// If nothing is stubbed, it defaults to default time behavior in the time
63−// package.
64−type Clock struct {
65− mock *clockapi.Mock
66−}
67−
68−// Set sets the Clock to a clock.Mock at the given time.Time
69−func (c *Clock) Set(t time.Time) {
70− if c.mock == nil {
71− c.mock = clockapi.NewMock()
72− }
73− c.mock.Set(t)
74−}
75−
76−// Add moves clock forward time.Duration if it is mocked. It will error
77−// if the clock is not mocked.
78−func (c *Clock) Add(d time.Duration) error {
79− if c.mock == nil {
80− return errors.New("clock not mocked")
81− }
82− c.mock.Add(d)
83− return nil
84−}
85−
86−// Reset removes local clock.Mock. Returns any existing Mock if set in case
87−// lingering time operations are attached to it.
88−func (c *Clock) Reset() *clockapi.Mock {
89− existing := c.mock
90− c.mock = nil
91− return existing
92−}
93−
94−func (c *Clock) After(d time.Duration) <-chan time.Time {
95− m := c.mock
96− if m == nil {
97− return globalClock.After(d)
98− }
99− return m.After(d)
100−}
101−
102−func (c *Clock) AfterFunc(d time.Duration, f func()) *clockapi.Timer {
103− m := c.mock
104− if m == nil {
105− return globalClock.AfterFunc(d, f)
106− }
107− return m.AfterFunc(d, f)
108−}
109−
110−func (c *Clock) Now() time.Time {
111− m := c.mock
112− if m == nil {
113− return globalClock.Now()
114− }
115− return m.Now()
116−}
117−
118−func (c *Clock) Since(t time.Time) time.Duration {
119− m := c.mock
120− if m == nil {
121− return globalClock.Since(t)
122− }
123− return m.Since(t)
124−}
125−
126−func (c *Clock) Sleep(d time.Duration) {
127− m := c.mock
128− if m == nil {
129− globalClock.Sleep(d)
130− return
131− }
132− m.Sleep(d)
133−}
134−
135−func (c *Clock) Tick(d time.Duration) <-chan time.Time {
136− m := c.mock
137− if m == nil {
138− return globalClock.Tick(d)
139− }
140− return m.Tick(d)
141−}
142−
143−func (c *Clock) Ticker(d time.Duration) *clockapi.Ticker {
144− m := c.mock
145− if m == nil {
146− return globalClock.Ticker(d)
147− }
148− return m.Ticker(d)
149−}
150−
151−func (c *Clock) Timer(d time.Duration) *clockapi.Timer {
152− m := c.mock
153− if m == nil {
154− return globalClock.Timer(d)
155− }
156− return m.Timer(d)
157−}
pkg/clock/clock_suite_test.godeleted−17
@@ -1,17 +0,0 @@
1−package clock_test
2−
3−import (
4− "testing"
5−
6− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
7− . "github.com/onsi/ginkgo/v2"
8− . "github.com/onsi/gomega"
9−)
10−
11−func TestClockSuite(t *testing.T) {
12− logger.SetOutput(GinkgoWriter)
13− logger.SetErrOutput(GinkgoWriter)
14−
15− RegisterFailHandler(Fail)
16− RunSpecs(t, "Clock")
17−}
pkg/clock/clock_test.godeleted−380
@@ -1,380 +0,0 @@
1−package clock_test
2−
3−import (
4− "sync"
5− "sync/atomic"
6− "time"
7−
8− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/clock"
9− . "github.com/onsi/ginkgo/v2"
10− . "github.com/onsi/gomega"
11−)
12−
13−const (
14− testGlobalEpoch = 1000000000
15− testLocalEpoch = 1234567890
16−)
17−
18−var _ = Describe("Clock suite", func() {
19− var testClock = clock.Clock{}
20−
21− AfterEach(func() {
22− clock.Reset()
23− testClock.Reset()
24− })
25−
26− Context("Global time not overridden", func() {
27− It("errors when trying to Add", func() {
28− err := clock.Add(123 * time.Hour)
29− Expect(err).To(HaveOccurred())
30− })
31−
32− Context("Clock not mocked via Set", func() {
33− const (
34− outsideTolerance = int32(0)
35− withinTolerance = int32(1)
36− )
37−
38− It("uses time.After for After", func() {
39− var tolerance int32
40− go func() {
41− time.Sleep(10 * time.Millisecond)
42− atomic.StoreInt32(&tolerance, withinTolerance)
43− }()
44− go func() {
45− time.Sleep(30 * time.Millisecond)
46− atomic.StoreInt32(&tolerance, outsideTolerance)
47− }()
48−
49− Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
50−
51− <-testClock.After(20 * time.Millisecond)
52− Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
53−
54− <-testClock.After(20 * time.Millisecond)
55− Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
56− })
57−
58− It("uses time.AfterFunc for AfterFunc", func() {
59− var tolerance int32
60− go func() {
61− time.Sleep(10 * time.Millisecond)
62− atomic.StoreInt32(&tolerance, withinTolerance)
63− }()
64− go func() {
65− time.Sleep(30 * time.Millisecond)
66− atomic.StoreInt32(&tolerance, outsideTolerance)
67− }()
68−
69− Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
70−
71− var wg sync.WaitGroup
72− wg.Add(1)
73− testClock.AfterFunc(20*time.Millisecond, func() {
74− wg.Done()
75− })
76− wg.Wait()
77− Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
78−
79− wg.Add(1)
80− testClock.AfterFunc(20*time.Millisecond, func() {
81− wg.Done()
82− })
83− wg.Wait()
84− Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
85− })
86−
87− It("uses time.Now for Now", func() {
88− a := time.Now()
89− b := testClock.Now()
90− Expect(b.Sub(a).Round(10 * time.Millisecond)).To(Equal(0 * time.Millisecond))
91− })
92−
93− It("uses time.Since for Since", func() {
94− past := time.Now().Add(-60 * time.Second)
95− Expect(time.Since(past).Round(10 * time.Millisecond)).
96− To(Equal(60 * time.Second))
97− })
98−
99− It("uses time.Sleep for Sleep", func() {
100− var tolerance int32
101− go func() {
102− time.Sleep(10 * time.Millisecond)
103− atomic.StoreInt32(&tolerance, withinTolerance)
104− }()
105− go func() {
106− time.Sleep(30 * time.Millisecond)
107− atomic.StoreInt32(&tolerance, outsideTolerance)
108− }()
109−
110− Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
111−
112− testClock.Sleep(20 * time.Millisecond)
113− Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
114−
115− testClock.Sleep(20 * time.Millisecond)
116− Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
117− })
118−
119− It("uses time.Tick for Tick", func() {
120− var tolerance int32
121− go func() {
122− time.Sleep(10 * time.Millisecond)
123− atomic.StoreInt32(&tolerance, withinTolerance)
124− }()
125− go func() {
126− time.Sleep(50 * time.Millisecond)
127− atomic.StoreInt32(&tolerance, outsideTolerance)
128− }()
129−
130− ch := testClock.Tick(20 * time.Millisecond)
131− Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
132− <-ch
133− Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
134− <-ch
135− Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
136− <-ch
137− Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
138− })
139−
140− It("uses time.Ticker for Ticker", func() {
141− var tolerance int32
142− go func() {
143− time.Sleep(10 * time.Millisecond)
144− atomic.StoreInt32(&tolerance, withinTolerance)
145− }()
146− go func() {
147− time.Sleep(50 * time.Millisecond)
148− atomic.StoreInt32(&tolerance, outsideTolerance)
149− }()
150−
151− ticker := testClock.Ticker(20 * time.Millisecond)
152− Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
153− <-ticker.C
154− Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
155− <-ticker.C
156− Expect(atomic.LoadInt32(&tolerance)).To(Equal(withinTolerance))
157− <-ticker.C
158− Expect(atomic.LoadInt32(&tolerance)).To(Equal(outsideTolerance))
159− })
160−
161− It("errors if Add is used", func() {
162− err := testClock.Add(100 * time.Second)
163− Expect(err).To(HaveOccurred())
164− })
165− })
166−
167− Context("Clock mocked via Set", func() {
168− var now = time.Unix(testLocalEpoch, 0)
169−
170− BeforeEach(func() {
171− testClock.Set(now)
172− })
173−
174− It("mocks After", func() {
175− var after int32
176− ready := make(chan struct{})
177− ch := testClock.After(10 * time.Second)
178− go func(ch <-chan time.Time) {
179− close(ready)
180− <-ch
181− atomic.StoreInt32(&after, 1)
182− }(ch)
183− <-ready
184−
185− err := testClock.Add(9 * time.Second)
186− Expect(err).ToNot(HaveOccurred())
187− Expect(atomic.LoadInt32(&after)).To(Equal(int32(0)))
188−
189− err = testClock.Add(1 * time.Second)
190− Expect(err).ToNot(HaveOccurred())
191− Expect(atomic.LoadInt32(&after)).To(Equal(int32(1)))
192− })
193−
194− It("mocks AfterFunc", func() {
195− var after int32
196− testClock.AfterFunc(10*time.Second, func() {
197− atomic.StoreInt32(&after, 1)
198− })
199−
200− err := testClock.Add(9 * time.Second)
201− Expect(err).ToNot(HaveOccurred())
202− Expect(atomic.LoadInt32(&after)).To(Equal(int32(0)))
203−
204− err = testClock.Add(1 * time.Second)
205− Expect(err).ToNot(HaveOccurred())
206− Expect(atomic.LoadInt32(&after)).To(Equal(int32(1)))
207− })
208−
209− It("mocks AfterFunc with a stopped timer", func() {
210− var after int32
211− timer := testClock.AfterFunc(10*time.Second, func() {
212− atomic.StoreInt32(&after, 1)
213− })
214− timer.Stop()
215−
216− err := testClock.Add(11 * time.Second)
217− Expect(err).ToNot(HaveOccurred())
218− Expect(atomic.LoadInt32(&after)).To(Equal(int32(0)))
219− })
220−
221− It("mocks Now", func() {
222− Expect(testClock.Now()).To(Equal(now))
223− err := testClock.Add(123 * time.Hour)
224− Expect(err).ToNot(HaveOccurred())
225− Expect(testClock.Now()).To(Equal(now.Add(123 * time.Hour)))
226− })
227−
228− It("mocks Since", func() {
229− Expect(testClock.Since(time.Unix(testLocalEpoch-100, 0))).
230− To(Equal(100 * time.Second))
231− })
232−
233− It("mocks Sleep", func() {
234− var after int32
235− ready := make(chan struct{})
236− go func() {
237− close(ready)
238− testClock.Sleep(10 * time.Second)
239− atomic.StoreInt32(&after, 1)
240− }()
241− <-ready
242−
243− err := testClock.Add(9 * time.Second)
244− Expect(err).ToNot(HaveOccurred())
245− Expect(atomic.LoadInt32(&after)).To(Equal(int32(0)))
246−
247− err = testClock.Add(1 * time.Second)
248− Expect(err).ToNot(HaveOccurred())
249− Expect(atomic.LoadInt32(&after)).To(Equal(int32(1)))
250− })
251−
252− It("mocks Tick", func() {
253− var ticks int32
254− ready := make(chan struct{})
255− go func() {
256− close(ready)
257− tick := testClock.Tick(10 * time.Second)
258− for ticks < 5 {
259− <-tick
260− atomic.AddInt32(&ticks, 1)
261− }
262− }()
263− <-ready
264−
265− Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(0)))
266−
267− err := testClock.Add(9 * time.Second)
268− Expect(err).ToNot(HaveOccurred())
269− Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(0)))
270−
271− err = testClock.Add(1 * time.Second)
272− Expect(err).ToNot(HaveOccurred())
273− Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(1)))
274−
275− err = testClock.Add(30 * time.Second)
276− Expect(err).ToNot(HaveOccurred())
277− Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(4)))
278−
279− err = testClock.Add(10 * time.Second)
280− Expect(err).ToNot(HaveOccurred())
281− Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(5)))
282− })
283−
284− It("mocks Ticker", func() {
285− var ticks int32
286− ready := make(chan struct{})
287− go func() {
288− ticker := testClock.Ticker(10 * time.Second)
289− close(ready)
290− for ticks < 5 {
291− <-ticker.C
292− atomic.AddInt32(&ticks, 1)
293− }
294− }()
295− <-ready
296−
297− Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(0)))
298−
299− err := testClock.Add(9 * time.Second)
300− Expect(err).ToNot(HaveOccurred())
301− Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(0)))
302−
303− err = testClock.Add(1 * time.Second)
304− Expect(err).ToNot(HaveOccurred())
305− Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(1)))
306−
307− err = testClock.Add(30 * time.Second)
308− Expect(err).ToNot(HaveOccurred())
309− Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(4)))
310−
311− err = testClock.Add(10 * time.Second)
312− Expect(err).ToNot(HaveOccurred())
313− Expect(atomic.LoadInt32(&ticks)).To(Equal(int32(5)))
314− })
315−
316− It("mocks Timer", func() {
317− var after int32
318− ready := make(chan struct{})
319− go func() {
320− timer := testClock.Timer(10 * time.Second)
321− close(ready)
322− <-timer.C
323− atomic.AddInt32(&after, 1)
324− }()
325− <-ready
326−
327− err := testClock.Add(9 * time.Second)
328− Expect(err).ToNot(HaveOccurred())
329− Expect(atomic.LoadInt32(&after)).To(Equal(int32(0)))
330−
331− err = testClock.Add(1 * time.Second)
332− Expect(err).ToNot(HaveOccurred())
333− Expect(atomic.LoadInt32(&after)).To(Equal(int32(1)))
334− })
335− })
336− })
337−
338− Context("Global time overridden", func() {
339− var (
340− globalNow = time.Unix(testGlobalEpoch, 0)
341− localNow = time.Unix(testLocalEpoch, 0)
342− )
343−
344− BeforeEach(func() {
345− clock.Set(globalNow)
346− })
347−
348− Context("Clock not mocked via Set", func() {
349− It("uses globally mocked Now", func() {
350− Expect(testClock.Now()).To(Equal(globalNow))
351− err := clock.Add(123 * time.Hour)
352− Expect(err).ToNot(HaveOccurred())
353− Expect(testClock.Now()).To(Equal(globalNow.Add(123 * time.Hour)))
354− })
355−
356− It("errors when Add is called on the local Clock", func() {
357− err := testClock.Add(100 * time.Hour)
358− Expect(err).To(HaveOccurred())
359− })
360− })
361−
362− Context("Clock is mocked via Set", func() {
363− BeforeEach(func() {
364− testClock.Set(localNow)
365− })
366−
367− It("uses the local mock and ignores the global", func() {
368− Expect(testClock.Now()).To(Equal(localNow))
369−
370− err := clock.Add(456 * time.Hour)
371− Expect(err).ToNot(HaveOccurred())
372−
373− err = testClock.Add(123 * time.Hour)
374− Expect(err).ToNot(HaveOccurred())
375−
376− Expect(testClock.Now()).To(Equal(localNow.Add(123 * time.Hour)))
377− })
378− })
379− })
380−})
pkg/cookies/csrf.go+8−8
@@ -10,7 +10,6 @@ import (
1010
1111 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
1212 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
13− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/clock"
1413 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
1514 "github.com/vmihailenco/msgpack/v5"
1615)
@@ -47,7 +46,7 @@ type csrf struct {
4746 CodeVerifier string `msgpack:"cv,omitempty"`
4847
4948 cookieOpts *options.Cookie
50− time clock.Clock
49+ clock func() time.Time
5150}
5251
5352// csrtStateTrim will indicate the length of the state trimmed for the name of the csrf cookie
@@ -70,6 +69,7 @@ func NewCSRF(opts *options.Cookie, codeVerifier string) (CSRF, error) {
7069 CodeVerifier: codeVerifier,
7170
7271 cookieOpts: opts,
72+ clock: time.Now,
7373 }, nil
7474}
7575
@@ -187,7 +187,7 @@ func ClearExtraCsrfCookies(opts *options.Cookie, rw http.ResponseWriter, req *ht
187187
188188 // delete the X oldest cookies
189189 slices.SortStableFunc(decodedCookies, func(a, b *csrf) int {
190− return a.time.Now().Compare(b.time.Now())
190+ return a.clock().Compare(b.clock())
191191 })
192192
193193 for i := 0; i < len(decodedCookies)-opts.CSRFPerRequestLimit; i++ {
@@ -223,7 +223,7 @@ func (c *csrf) encodeCookie() (string, error) {
223223 if err != nil {
224224 return "", fmt.Errorf("error getting cookie secret: %v", err)
225225 }
226− return encryption.SignedValue(secret, c.cookieName(), encrypted, c.time.Now())
226+ return encryption.SignedValue(secret, c.cookieName(), encrypted, c.clock())
227227}
228228
229229// decodeCSRFCookie validates the signature then decrypts and decodes a CSRF
@@ -249,10 +249,10 @@ func decodeCSRFCookie(cookie *http.Cookie, opts *options.Cookie) (*csrf, error)
249249
250250// unmarshalCSRF unmarshals decrypted data into a CSRF struct
251251func unmarshalCSRF(decrypted []byte, opts *options.Cookie, csrfTime time.Time) (*csrf, error) {
252− clock := clock.Clock{}
253− clock.Set(csrfTime)
254−
255− csrf := &csrf{cookieOpts: opts, time: clock}
252+ csrf := &csrf{
253+ cookieOpts: opts,
254+ clock: func() time.Time { return csrfTime },
255+ }
256256 if err := msgpack.Unmarshal(decrypted, csrf); err != nil {
257257 return nil, fmt.Errorf("error unmarshalling data to CSRF: %v", err)
258258 }
pkg/cookies/csrf_per_request_test.go+5−5
@@ -128,7 +128,7 @@ var _ = Describe("CSRF Cookie with non-fixed name Tests", func() {
128128 testNow := time.Unix(nowEpoch, 0)
129129
130130 BeforeEach(func() {
131− privateCSRF.time.Set(testNow)
131+ privateCSRF.clock = func() time.Time { return testNow }
132132
133133 req = &http.Request{
134134 Method: http.MethodGet,
@@ -144,7 +144,7 @@ var _ = Describe("CSRF Cookie with non-fixed name Tests", func() {
144144 })
145145
146146 AfterEach(func() {
147− privateCSRF.time.Reset()
147+ privateCSRF.clock = time.Now
148148 })
149149
150150 Context("SetCookie", func() {
@@ -200,17 +200,17 @@ var _ = Describe("CSRF Cookie with non-fixed name Tests", func() {
200200 publicCSRF1, err := NewCSRF(cookieOpts, "verifier")
201201 Expect(err).ToNot(HaveOccurred())
202202 privateCSRF1 := publicCSRF1.(*csrf)
203− privateCSRF1.time.Set(testNow)
203+ privateCSRF1.clock = func() time.Time { return testNow }
204204
205205 publicCSRF2, err := NewCSRF(cookieOpts, "verifier")
206206 Expect(err).ToNot(HaveOccurred())
207207 privateCSRF2 := publicCSRF2.(*csrf)
208− privateCSRF2.time.Set(testNow.Add(time.Minute))
208+ privateCSRF2.clock = func() time.Time { return testNow.Add(time.Minute) }
209209
210210 publicCSRF3, err := NewCSRF(cookieOpts, "verifier")
211211 Expect(err).ToNot(HaveOccurred())
212212 privateCSRF3 := publicCSRF3.(*csrf)
213− privateCSRF3.time.Set(testNow.Add(time.Minute * 2))
213+ privateCSRF3.clock = func() time.Time { return testNow.Add(time.Minute * 2) }
214214
215215 cookies := []string{}
216216 for _, csrf := range []*csrf{privateCSRF1, privateCSRF2, privateCSRF3} {
pkg/cookies/csrf_test.go+3−3
@@ -130,7 +130,7 @@ var _ = Describe("CSRF Cookie Tests", func() {
130130 testNow := time.Unix(nowEpoch, 0)
131131
132132 BeforeEach(func() {
133− privateCSRF.time.Set(testNow)
133+ privateCSRF.clock = func() time.Time { return testNow }
134134
135135 req = &http.Request{
136136 Method: http.MethodGet,
@@ -146,7 +146,7 @@ var _ = Describe("CSRF Cookie Tests", func() {
146146 })
147147
148148 AfterEach(func() {
149− privateCSRF.time.Reset()
149+ privateCSRF.clock = time.Now
150150 })
151151
152152 Context("SetCookie", func() {
@@ -173,7 +173,7 @@ var _ = Describe("CSRF Cookie Tests", func() {
173173 Context("LoadCSRFCookie", func() {
174174 BeforeEach(func() {
175175 // we need to reset the time to ensure the cookie is valid
176− privateCSRF.time.Reset()
176+ privateCSRF.clock = time.Now
177177 })
178178
179179 It("should return error when no cookie is set", func() {
pkg/middleware/stored_session_test.go+15−10
@@ -11,7 +11,6 @@ import (
1111
1212 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1313 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
14− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/clock"
1514 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
1615 . "github.com/onsi/ginkgo/v2"
1716 . "github.com/onsi/gomega"
@@ -95,6 +94,7 @@ var _ = Describe("Stored Session Suite", func() {
9594 now := time.Now()
9695 createdPast := now.Add(-5 * time.Minute)
9796 createdFuture := now.Add(5 * time.Minute)
97+ clock := func() time.Time { return now }
9898
9999 var defaultRefreshFunc = func(_ context.Context, ss *sessionsapi.SessionState) (bool, error) {
100100 switch ss.RefreshToken {
@@ -120,6 +120,7 @@ var _ = Describe("Stored Session Suite", func() {
120120 RefreshToken: noRefresh,
121121 CreatedAt: &createdPast,
122122 ExpiresOn: &createdFuture,
123+ Clock: clock,
123124 }, nil
124125 case "_oauth2_proxy=InvalidNoRefreshSession":
125126 return &sessionsapi.SessionState{
@@ -127,24 +128,28 @@ var _ = Describe("Stored Session Suite", func() {
127128 RefreshToken: noRefresh,
128129 CreatedAt: &createdPast,
129130 ExpiresOn: &createdFuture,
131+ Clock: clock,
130132 }, nil
131133 case "_oauth2_proxy=ExpiredNoRefreshSession":
132134 return &sessionsapi.SessionState{
133135 RefreshToken: noRefresh,
134136 CreatedAt: &createdPast,
135137 ExpiresOn: &createdPast,
138+ Clock: clock,
136139 }, nil
137140 case "_oauth2_proxy=RefreshSession":
138141 return &sessionsapi.SessionState{
139142 RefreshToken: refresh,
140143 CreatedAt: &createdPast,
141144 ExpiresOn: &createdFuture,
145+ Clock: clock,
142146 }, nil
143147 case "_oauth2_proxy=RefreshError":
144148 return &sessionsapi.SessionState{
145149 RefreshToken: "RefreshError",
146150 CreatedAt: &createdPast,
147151 ExpiresOn: &createdFuture,
152+ Clock: clock,
148153 }, nil
149154 case "_oauth2_proxy=NonExistent":
150155 return nil, fmt.Errorf("invalid cookie")
@@ -154,14 +159,6 @@ var _ = Describe("Stored Session Suite", func() {
154159 },
155160 }
156161
157− BeforeEach(func() {
158− clock.Set(now)
159− })
160−
161− AfterEach(func() {
162− clock.Reset()
163− })
164−
165162 type storedSessionLoaderTableInput struct {
166163 requestHeaders http.Header
167164 existingSession *sessionsapi.SessionState
@@ -200,7 +197,15 @@ var _ = Describe("Stored Session Suite", func() {
200197 }))
201198 handler.ServeHTTP(rw, req)
202199
203− Expect(gotSession).To(Equal(in.expectedSession))
200+ // Compare, ignoring testing Clock.
201+ if in.expectedSession == nil {
202+ Expect(gotSession).To(BeNil())
203+ return
204+ }
205+ Expect(gotSession).ToNot(BeNil())
206+ got := *gotSession
207+ got.Clock = nil
208+ Expect(&got).To(Equal(in.expectedSession))
204209 },
205210 Entry("with no cookie", storedSessionLoaderTableInput{
206211 requestHeaders: http.Header{},
providers/provider_default_test.go+3−2
@@ -17,8 +17,9 @@ func TestRefresh(t *testing.T) {
1717 now := time.Unix(1234567890, 10)
1818 expires := time.Unix(1234567890, 0)
1919
20− ss := &sessions.SessionState{}
21− ss.Clock.Set(now)
20+ ss := &sessions.SessionState{
21+ Clock: func() time.Time { return now },
22+ }
2223 ss.SetExpiresOn(expires)
2324
2425 refreshed, err := p.RefreshSession(context.Background(), ss)