git.iko.soy/typeable/oauth2-proxy
on master help sign in
test: replace mock pkg/clock with narrowly targeted stub clocks. (#3238)
110d51dDavid Symonds
CHANGELOG.md+1
@@ -10,6 +10,7 @@
10
10
11- [#3228](https://github.com/oauth2-proxy/oauth2-proxy/pull/3228) fix: use GetSecret() in ticket.go makeCookie to respect cookie-secret-file (@stagswtf)
11- [#3228](https://github.com/oauth2-proxy/oauth2-proxy/pull/3228) fix: use GetSecret() in ticket.go makeCookie to respect cookie-secret-file (@stagswtf)
12- [#3244](https://github.com/oauth2-proxy/oauth2-proxy/pull/3244) chore(deps): upgrade to latest go1.25.3 (@tuunit)
12- [#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)
13
14
14# V7.12.0
15# V7.12.0
15
16
go.mod−1
@@ -7,7 +7,6 @@ require (
7 github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
7 github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
8 github.com/a8m/envsubst v1.4.3
8 github.com/a8m/envsubst v1.4.3
9 github.com/alicebob/miniredis/v2 v2.35.0
9 github.com/alicebob/miniredis/v2 v2.35.0
10− github.com/benbjohnson/clock v1.3.5
11 github.com/bitly/go-simplejson v0.5.1
10 github.com/bitly/go-simplejson v0.5.1
12 github.com/bsm/redislock v0.9.4
11 github.com/bsm/redislock v0.9.4
13 github.com/coreos/go-oidc/v3 v3.14.1
12 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
14github.com/alicebob/miniredis/v2 v2.11.1/go.mod h1:UA48pmi7aSazcGAvcdKcBB49z521IC9VjTTRz2nIaJE=
14github.com/alicebob/miniredis/v2 v2.11.1/go.mod h1:UA48pmi7aSazcGAvcdKcBB49z521IC9VjTTRz2nIaJE=
15github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
15github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
16github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
16github.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=
19github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
17github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
20github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
18github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
21github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
19github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
pkg/apis/sessions/session_state.go+12−6
@@ -7,7 +7,6 @@ import (
7 "io"
7 "io"
8 "time"
8 "time"
9
9
10− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/clock"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
10 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
12 "github.com/pierrec/lz4/v4"
11 "github.com/pierrec/lz4/v4"
13 "github.com/vmihailenco/msgpack/v5"
12 "github.com/vmihailenco/msgpack/v5"
@@ -30,8 +29,15 @@ type SessionState struct {
30 PreferredUsername string `msgpack:"pu,omitempty"`
29 PreferredUsername string `msgpack:"pu,omitempty"`
31
30
32 // Internal helpers, not serialized
31 // Internal helpers, not serialized
33− Clock clock.Clock `msgpack:"-"`
32+ Clock func() time.Time `msgpack:"-"` // override for time.Now, for testing
34− Lock Lock `msgpack:"-"`
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()
35}
41}
36
42
37func (s *SessionState) ObtainLock(ctx context.Context, expiration time.Duration) error {
43func (s *SessionState) ObtainLock(ctx context.Context, expiration time.Duration) error {
@@ -64,7 +70,7 @@ func (s *SessionState) PeekLock(ctx context.Context) (bool, error) {
64
70
65// CreatedAtNow sets a SessionState's CreatedAt to now
71// CreatedAtNow sets a SessionState's CreatedAt to now
66func (s *SessionState) CreatedAtNow() {
72func (s *SessionState) CreatedAtNow() {
67− now := s.Clock.Now()
73+ now := s.now()
68 s.CreatedAt = &now
74 s.CreatedAt = &now
69}
75}
70
76
@@ -85,7 +91,7 @@ func (s *SessionState) ExpiresIn(d time.Duration) {
85
91
86// IsExpired checks whether the session has expired
92// IsExpired checks whether the session has expired
87func (s *SessionState) IsExpired() bool {
93func (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()) {
89 return true
95 return true
90 }
96 }
91 return false
97 return false
@@ -94,7 +100,7 @@ func (s *SessionState) IsExpired() bool {
94// Age returns the age of a session
100// Age returns the age of a session
95func (s *SessionState) Age() time.Duration {
101func (s *SessionState) Age() time.Duration {
96 if s.CreatedAt != nil && !s.CreatedAt.IsZero() {
102 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)
98 }
104 }
99 return 0
105 return 0
100}
106}
pkg/apis/sessions/session_state_test.go+3−3
@@ -22,7 +22,7 @@ func TestCreatedAtNow(t *testing.T) {
22 ss := &SessionState{}
22 ss := &SessionState{}
23
23
24 now := time.Unix(1234567890, 0)
24 now := time.Unix(1234567890, 0)
25− ss.Clock.Set(now)
25+ ss.Clock = func() time.Time { return now }
26
26
27 ss.CreatedAtNow()
27 ss.CreatedAtNow()
28 g.Expect(*ss.CreatedAt).To(Equal(now))
28 g.Expect(*ss.CreatedAt).To(Equal(now))
@@ -33,9 +33,9 @@ func TestExpiresIn(t *testing.T) {
33 ss := &SessionState{}
33 ss := &SessionState{}
34
34
35 now := time.Unix(1234567890, 0)
35 now := time.Unix(1234567890, 0)
36− ss.Clock.Set(now)
36+ ss.Clock = func() time.Time { return now }
37
37
38− ttl := time.Duration(743) * time.Second
38+ ttl := 743 * time.Second
39 ss.ExpiresIn(ttl)
39 ss.ExpiresIn(ttl)
40
40
41 g.Expect(*ss.ExpiresOn).To(Equal(ss.CreatedAt.Add(ttl)))
41 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 (
10
10
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
13− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/clock"
14 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
13 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
15 "github.com/vmihailenco/msgpack/v5"
14 "github.com/vmihailenco/msgpack/v5"
16)
15)
@@ -47,7 +46,7 @@ type csrf struct {
47 CodeVerifier string `msgpack:"cv,omitempty"`
46 CodeVerifier string `msgpack:"cv,omitempty"`
48
47
49 cookieOpts *options.Cookie
48 cookieOpts *options.Cookie
50− time clock.Clock
49+ clock func() time.Time
51}
50}
52
51
53// csrtStateTrim will indicate the length of the state trimmed for the name of the csrf cookie
52// 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) {
70 CodeVerifier: codeVerifier,
69 CodeVerifier: codeVerifier,
71
70
72 cookieOpts: opts,
71 cookieOpts: opts,
72+ clock: time.Now,
73 }, nil
73 }, nil
74}
74}
75
75
@@ -187,7 +187,7 @@ func ClearExtraCsrfCookies(opts *options.Cookie, rw http.ResponseWriter, req *ht
187
187
188 // delete the X oldest cookies
188 // delete the X oldest cookies
189 slices.SortStableFunc(decodedCookies, func(a, b *csrf) int {
189 slices.SortStableFunc(decodedCookies, func(a, b *csrf) int {
190− return a.time.Now().Compare(b.time.Now())
190+ return a.clock().Compare(b.clock())
191 })
191 })
192
192
193 for i := 0; i < len(decodedCookies)-opts.CSRFPerRequestLimit; i++ {
193 for i := 0; i < len(decodedCookies)-opts.CSRFPerRequestLimit; i++ {
@@ -223,7 +223,7 @@ func (c *csrf) encodeCookie() (string, error) {
223 if err != nil {
223 if err != nil {
224 return "", fmt.Errorf("error getting cookie secret: %v", err)
224 return "", fmt.Errorf("error getting cookie secret: %v", err)
225 }
225 }
226− return encryption.SignedValue(secret, c.cookieName(), encrypted, c.time.Now())
226+ return encryption.SignedValue(secret, c.cookieName(), encrypted, c.clock())
227}
227}
228
228
229// decodeCSRFCookie validates the signature then decrypts and decodes a CSRF
229// decodeCSRFCookie validates the signature then decrypts and decodes a CSRF
@@ -249,10 +249,10 @@ func decodeCSRFCookie(cookie *http.Cookie, opts *options.Cookie) (*csrf, error)
249
249
250// unmarshalCSRF unmarshals decrypted data into a CSRF struct
250// unmarshalCSRF unmarshals decrypted data into a CSRF struct
251func unmarshalCSRF(decrypted []byte, opts *options.Cookie, csrfTime time.Time) (*csrf, error) {
251func unmarshalCSRF(decrypted []byte, opts *options.Cookie, csrfTime time.Time) (*csrf, error) {
252− clock := clock.Clock{}
252+ csrf := &csrf{
253− clock.Set(csrfTime)
254−
255− csrf := &csrf{cookieOpts: opts, time: clock}
253+ cookieOpts: opts,
254+ clock: func() time.Time { return csrfTime },
255+ }
256 if err := msgpack.Unmarshal(decrypted, csrf); err != nil {
256 if err := msgpack.Unmarshal(decrypted, csrf); err != nil {
257 return nil, fmt.Errorf("error unmarshalling data to CSRF: %v", err)
257 return nil, fmt.Errorf("error unmarshalling data to CSRF: %v", err)
258 }
258 }
pkg/cookies/csrf_per_request_test.go+5−5
@@ -128,7 +128,7 @@ var _ = Describe("CSRF Cookie with non-fixed name Tests", func() {
128 testNow := time.Unix(nowEpoch, 0)
128 testNow := time.Unix(nowEpoch, 0)
129
129
130 BeforeEach(func() {
130 BeforeEach(func() {
131− privateCSRF.time.Set(testNow)
131+ privateCSRF.clock = func() time.Time { return testNow }
132
132
133 req = &http.Request{
133 req = &http.Request{
134 Method: http.MethodGet,
134 Method: http.MethodGet,
@@ -144,7 +144,7 @@ var _ = Describe("CSRF Cookie with non-fixed name Tests", func() {
144 })
144 })
145
145
146 AfterEach(func() {
146 AfterEach(func() {
147− privateCSRF.time.Reset()
147+ privateCSRF.clock = time.Now
148 })
148 })
149
149
150 Context("SetCookie", func() {
150 Context("SetCookie", func() {
@@ -200,17 +200,17 @@ var _ = Describe("CSRF Cookie with non-fixed name Tests", func() {
200 publicCSRF1, err := NewCSRF(cookieOpts, "verifier")
200 publicCSRF1, err := NewCSRF(cookieOpts, "verifier")
201 Expect(err).ToNot(HaveOccurred())
201 Expect(err).ToNot(HaveOccurred())
202 privateCSRF1 := publicCSRF1.(*csrf)
202 privateCSRF1 := publicCSRF1.(*csrf)
203− privateCSRF1.time.Set(testNow)
203+ privateCSRF1.clock = func() time.Time { return testNow }
204
204
205 publicCSRF2, err := NewCSRF(cookieOpts, "verifier")
205 publicCSRF2, err := NewCSRF(cookieOpts, "verifier")
206 Expect(err).ToNot(HaveOccurred())
206 Expect(err).ToNot(HaveOccurred())
207 privateCSRF2 := publicCSRF2.(*csrf)
207 privateCSRF2 := publicCSRF2.(*csrf)
208− privateCSRF2.time.Set(testNow.Add(time.Minute))
208+ privateCSRF2.clock = func() time.Time { return testNow.Add(time.Minute) }
209
209
210 publicCSRF3, err := NewCSRF(cookieOpts, "verifier")
210 publicCSRF3, err := NewCSRF(cookieOpts, "verifier")
211 Expect(err).ToNot(HaveOccurred())
211 Expect(err).ToNot(HaveOccurred())
212 privateCSRF3 := publicCSRF3.(*csrf)
212 privateCSRF3 := publicCSRF3.(*csrf)
213− privateCSRF3.time.Set(testNow.Add(time.Minute * 2))
213+ privateCSRF3.clock = func() time.Time { return testNow.Add(time.Minute * 2) }
214
214
215 cookies := []string{}
215 cookies := []string{}
216 for _, csrf := range []*csrf{privateCSRF1, privateCSRF2, privateCSRF3} {
216 for _, csrf := range []*csrf{privateCSRF1, privateCSRF2, privateCSRF3} {
pkg/cookies/csrf_test.go+3−3
@@ -130,7 +130,7 @@ var _ = Describe("CSRF Cookie Tests", func() {
130 testNow := time.Unix(nowEpoch, 0)
130 testNow := time.Unix(nowEpoch, 0)
131
131
132 BeforeEach(func() {
132 BeforeEach(func() {
133− privateCSRF.time.Set(testNow)
133+ privateCSRF.clock = func() time.Time { return testNow }
134
134
135 req = &http.Request{
135 req = &http.Request{
136 Method: http.MethodGet,
136 Method: http.MethodGet,
@@ -146,7 +146,7 @@ var _ = Describe("CSRF Cookie Tests", func() {
146 })
146 })
147
147
148 AfterEach(func() {
148 AfterEach(func() {
149− privateCSRF.time.Reset()
149+ privateCSRF.clock = time.Now
150 })
150 })
151
151
152 Context("SetCookie", func() {
152 Context("SetCookie", func() {
@@ -173,7 +173,7 @@ var _ = Describe("CSRF Cookie Tests", func() {
173 Context("LoadCSRFCookie", func() {
173 Context("LoadCSRFCookie", func() {
174 BeforeEach(func() {
174 BeforeEach(func() {
175 // we need to reset the time to ensure the cookie is valid
175 // we need to reset the time to ensure the cookie is valid
176− privateCSRF.time.Reset()
176+ privateCSRF.clock = time.Now
177 })
177 })
178
178
179 It("should return error when no cookie is set", func() {
179 It("should return error when no cookie is set", func() {
pkg/middleware/stored_session_test.go+15−10
@@ -11,7 +11,6 @@ import (
11
11
12 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
12 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
13 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
13 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
14− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/clock"
15 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
14 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
16 . "github.com/onsi/ginkgo/v2"
15 . "github.com/onsi/ginkgo/v2"
17 . "github.com/onsi/gomega"
16 . "github.com/onsi/gomega"
@@ -95,6 +94,7 @@ var _ = Describe("Stored Session Suite", func() {
95 now := time.Now()
94 now := time.Now()
96 createdPast := now.Add(-5 * time.Minute)
95 createdPast := now.Add(-5 * time.Minute)
97 createdFuture := now.Add(5 * time.Minute)
96 createdFuture := now.Add(5 * time.Minute)
97+ clock := func() time.Time { return now }
98
98
99 var defaultRefreshFunc = func(_ context.Context, ss *sessionsapi.SessionState) (bool, error) {
99 var defaultRefreshFunc = func(_ context.Context, ss *sessionsapi.SessionState) (bool, error) {
100 switch ss.RefreshToken {
100 switch ss.RefreshToken {
@@ -120,6 +120,7 @@ var _ = Describe("Stored Session Suite", func() {
120 RefreshToken: noRefresh,
120 RefreshToken: noRefresh,
121 CreatedAt: &createdPast,
121 CreatedAt: &createdPast,
122 ExpiresOn: &createdFuture,
122 ExpiresOn: &createdFuture,
123+ Clock: clock,
123 }, nil
124 }, nil
124 case "_oauth2_proxy=InvalidNoRefreshSession":
125 case "_oauth2_proxy=InvalidNoRefreshSession":
125 return &sessionsapi.SessionState{
126 return &sessionsapi.SessionState{
@@ -127,24 +128,28 @@ var _ = Describe("Stored Session Suite", func() {
127 RefreshToken: noRefresh,
128 RefreshToken: noRefresh,
128 CreatedAt: &createdPast,
129 CreatedAt: &createdPast,
129 ExpiresOn: &createdFuture,
130 ExpiresOn: &createdFuture,
131+ Clock: clock,
130 }, nil
132 }, nil
131 case "_oauth2_proxy=ExpiredNoRefreshSession":
133 case "_oauth2_proxy=ExpiredNoRefreshSession":
132 return &sessionsapi.SessionState{
134 return &sessionsapi.SessionState{
133 RefreshToken: noRefresh,
135 RefreshToken: noRefresh,
134 CreatedAt: &createdPast,
136 CreatedAt: &createdPast,
135 ExpiresOn: &createdPast,
137 ExpiresOn: &createdPast,
138+ Clock: clock,
136 }, nil
139 }, nil
137 case "_oauth2_proxy=RefreshSession":
140 case "_oauth2_proxy=RefreshSession":
138 return &sessionsapi.SessionState{
141 return &sessionsapi.SessionState{
139 RefreshToken: refresh,
142 RefreshToken: refresh,
140 CreatedAt: &createdPast,
143 CreatedAt: &createdPast,
141 ExpiresOn: &createdFuture,
144 ExpiresOn: &createdFuture,
145+ Clock: clock,
142 }, nil
146 }, nil
143 case "_oauth2_proxy=RefreshError":
147 case "_oauth2_proxy=RefreshError":
144 return &sessionsapi.SessionState{
148 return &sessionsapi.SessionState{
145 RefreshToken: "RefreshError",
149 RefreshToken: "RefreshError",
146 CreatedAt: &createdPast,
150 CreatedAt: &createdPast,
147 ExpiresOn: &createdFuture,
151 ExpiresOn: &createdFuture,
152+ Clock: clock,
148 }, nil
153 }, nil
149 case "_oauth2_proxy=NonExistent":
154 case "_oauth2_proxy=NonExistent":
150 return nil, fmt.Errorf("invalid cookie")
155 return nil, fmt.Errorf("invalid cookie")
@@ -154,14 +159,6 @@ var _ = Describe("Stored Session Suite", func() {
154 },
159 },
155 }
160 }
156
161
157− BeforeEach(func() {
158− clock.Set(now)
159− })
160−
161− AfterEach(func() {
162− clock.Reset()
163− })
164−
165 type storedSessionLoaderTableInput struct {
162 type storedSessionLoaderTableInput struct {
166 requestHeaders http.Header
163 requestHeaders http.Header
167 existingSession *sessionsapi.SessionState
164 existingSession *sessionsapi.SessionState
@@ -200,7 +197,15 @@ var _ = Describe("Stored Session Suite", func() {
200 }))
197 }))
201 handler.ServeHTTP(rw, req)
198 handler.ServeHTTP(rw, req)
202
199
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
203− Expect(gotSession).To(Equal(in.expectedSession))
208+ Expect(&got).To(Equal(in.expectedSession))
204 },
209 },
205 Entry("with no cookie", storedSessionLoaderTableInput{
210 Entry("with no cookie", storedSessionLoaderTableInput{
206 requestHeaders: http.Header{},
211 requestHeaders: http.Header{},
providers/provider_default_test.go+3−2
@@ -17,8 +17,9 @@ func TestRefresh(t *testing.T) {
17 now := time.Unix(1234567890, 10)
17 now := time.Unix(1234567890, 10)
18 expires := time.Unix(1234567890, 0)
18 expires := time.Unix(1234567890, 0)
19
19
20− ss := &sessions.SessionState{}
20+ ss := &sessions.SessionState{
21− ss.Clock.Set(now)
21+ Clock: func() time.Time { return now },
22+ }
22 ss.SetExpiresOn(expires)
23 ss.SetExpiresOn(expires)
23
24
24 refreshed, err := p.RefreshSession(context.Background(), ss)
25 refreshed, err := p.RefreshSession(context.Background(), ss)