git.iko.soy/typeable/oauth2-proxy
on master help sign in
Manage session time fields centrally
commit7fa6d2d024b4a46d477bc1a4da58e377ee60fbce authorNick Meves <nick.meves@greenhouse.io> dateSat 6 Mar 2021, 15:33 (6 years ago) parent7e80e55
11 files changed+91−64
oauthproxy.go+12−3 pkg/apis/sessions/session_state.go+26−3 pkg/middleware/stored_session.go+1−2 pkg/sessions/cookie/session_store.go+1−2 pkg/sessions/persistence/manager.go+1−2 providers/azure.go+12−18 providers/gitlab.go+7−5 providers/google.go+12−13 providers/logingov.go+8−9 providers/oidc.go+5−4 providers/provider_default.go+6−3
oauthproxy.go+12−3
@@ -786,6 +786,15 @@ func (p *OAuthProxy) redeemCode(req *http.Request) (*sessionsapi.SessionState, e
786786 if err != nil {
787787 return nil, err
788788 }
789+
790+ // Force setting these in case the Provider didn't
791+ if s.CreatedAt == nil {
792+ s.CreatedAtNow()
793+ }
794+ if s.ExpiresOn == nil {
795+ s.ExpiresIn(p.CookieOptions.Expire)
796+ }
797+
789798 return s, nil
790799}
791800
@@ -861,9 +870,9 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
861870
862871// See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en
863872var noCacheHeaders = map[string]string{
864− "Expires": time.Unix(0, 0).Format(time.RFC1123),
865− "Cache-Control": "no-cache, no-store, must-revalidate, max-age=0",
866− "X-Accel-Expires": "0", // https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/
873+ "Expires": time.Unix(0, 0).Format(time.RFC1123),
874+ "Cache-Control": "no-cache, no-store, must-revalidate, max-age=0",
875+ "X-Accel-Expire": "0", // https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/
867876}
868877
869878// prepareNoCache prepares headers for preventing browser caching.
pkg/apis/sessions/session_state.go+26−3
@@ -11,6 +11,7 @@ import (
1111 "time"
1212 "unicode/utf8"
1313
14+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/clock"
1415 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
1516 "github.com/pierrec/lz4"
1617 "github.com/vmihailenco/msgpack/v4"
@@ -32,7 +33,8 @@ type SessionState struct {
3233 Groups []string `msgpack:"g,omitempty"`
3334 PreferredUsername string `msgpack:"pu,omitempty"`
3435
35− Lock Lock `msgpack:"-"`
36+ Clock clock.Clock `msgpack:"-"`
37+ Lock Lock `msgpack:"-"`
3638}
3739
3840func (s *SessionState) ObtainLock(ctx context.Context, expiration time.Duration) error {
@@ -63,9 +65,30 @@ func (s *SessionState) PeekLock(ctx context.Context) (bool, error) {
6365 return s.Lock.Peek(ctx)
6466}
6567
68+// CreatedAtNow sets a SessionState's CreatedAt to now
69+func (s *SessionState) CreatedAtNow() {
70+ now := s.Clock.Now()
71+ s.CreatedAt = &now
72+}
73+
74+// SetExpiresOn sets an expiration
75+func (s *SessionState) SetExpiresOn(exp time.Time) {
76+ s.ExpiresOn = &exp
77+}
78+
79+// ExpiresIn sets an expiration a certain duration from CreatedAt.
80+// CreatedAt will be set to time.Now if it is unset.
81+func (s *SessionState) ExpiresIn(d time.Duration) {
82+ if s.CreatedAt == nil {
83+ s.CreatedAtNow()
84+ }
85+ exp := s.CreatedAt.Add(d)
86+ s.ExpiresOn = &exp
87+}
88+
6689// IsExpired checks whether the session has expired
6790func (s *SessionState) IsExpired() bool {
68− if s.ExpiresOn != nil && !s.ExpiresOn.IsZero() && s.ExpiresOn.Before(time.Now()) {
91+ if s.ExpiresOn != nil && !s.ExpiresOn.IsZero() && s.ExpiresOn.Before(s.Clock.Now()) {
6992 return true
7093 }
7194 return false
@@ -74,7 +97,7 @@ func (s *SessionState) IsExpired() bool {
7497// Age returns the age of a session
7598func (s *SessionState) Age() time.Duration {
7699 if s.CreatedAt != nil && !s.CreatedAt.IsZero() {
77− return time.Now().Truncate(time.Second).Sub(*s.CreatedAt)
100+ return s.Clock.Now().Truncate(time.Second).Sub(*s.CreatedAt)
78101 }
79102 return 0
80103}
pkg/middleware/stored_session.go+1−2
@@ -142,8 +142,7 @@ func (s *storedSessionLoader) refreshSession(rw http.ResponseWriter, req *http.R
142142 }
143143
144144 // If we refreshed, update the `CreatedAt` time to reset the refresh timer
145− // TODO: Implement
146− // session.CreatedAtNow()
145+ session.CreatedAtNow()
147146
148147 // Because the session was refreshed, make sure to save it
149148 err = s.store.Save(rw, req, session)
pkg/sessions/cookie/session_store.go+1−2
@@ -36,8 +36,7 @@ type SessionStore struct {
3636// within Cookies set on the HTTP response writer
3737func (s *SessionStore) Save(rw http.ResponseWriter, req *http.Request, ss *sessions.SessionState) error {
3838 if ss.CreatedAt == nil || ss.CreatedAt.IsZero() {
39− now := time.Now()
40− ss.CreatedAt = &now
39+ ss.CreatedAtNow()
4140 }
4241 value, err := s.cookieForSession(ss)
4342 if err != nil {
pkg/sessions/persistence/manager.go+1−2
@@ -30,8 +30,7 @@ func NewManager(store Store, cookieOpts *options.Cookie) *Manager {
3030// from the persistent data store.
3131func (m *Manager) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error {
3232 if s.CreatedAt == nil || s.CreatedAt.IsZero() {
33− now := time.Now()
34− s.CreatedAt = &now
33+ s.CreatedAtNow()
3534 }
3635
3736 tckt, err := decodeTicketFromRequest(req, m.Options)
providers/azure.go+12−18
@@ -142,16 +142,13 @@ func (p *AzureProvider) Redeem(ctx context.Context, redirectURL, code string) (*
142142 return nil, err
143143 }
144144
145− created := time.Now()
146− expires := time.Unix(jsonResponse.ExpiresOn, 0)
147−
148145 session := &sessions.SessionState{
149146 AccessToken: jsonResponse.AccessToken,
150147 IDToken: jsonResponse.IDToken,
151− CreatedAt: &created,
152− ExpiresOn: &expires,
153148 RefreshToken: jsonResponse.RefreshToken,
154149 }
150+ session.CreatedAtNow()
151+ session.SetExpiresOn(time.Unix(jsonResponse.ExpiresOn, 0))
155152
156153 email, err := p.verifyTokenAndExtractEmail(ctx, session.IDToken)
157154
@@ -239,10 +236,9 @@ func (p *AzureProvider) verifyTokenAndExtractEmail(ctx context.Context, token st
239236 return email, nil
240237}
241238
242−// RefreshSessionIfNeeded checks if the session has expired and uses the
243−// RefreshToken to fetch a new ID token if required
244−func (p *AzureProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) {
245− if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
239+// RefreshSession uses the RefreshToken to fetch new Access and ID Tokens
240+func (p *AzureProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
241+ if s == nil || s.RefreshToken == "" {
246242 return false, nil
247243 }
248244
@@ -257,7 +253,7 @@ func (p *AzureProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.
257253 return true, nil
258254}
259255
260−func (p *AzureProvider) redeemRefreshToken(ctx context.Context, s *sessions.SessionState) (err error) {
256+func (p *AzureProvider) redeemRefreshToken(ctx context.Context, s *sessions.SessionState) error {
261257 params := url.Values{}
262258 params.Add("client_id", p.ClientID)
263259 params.Add("client_secret", p.ClientSecret)
@@ -271,25 +267,23 @@ func (p *AzureProvider) redeemRefreshToken(ctx context.Context, s *sessions.Sess
271267 IDToken string `json:"id_token"`
272268 }
273269
274− err = requests.New(p.RedeemURL.String()).
270+ err := requests.New(p.RedeemURL.String()).
275271 WithContext(ctx).
276272 WithMethod("POST").
277273 WithBody(bytes.NewBufferString(params.Encode())).
278274 SetHeader("Content-Type", "application/x-www-form-urlencoded").
279275 Do().
280276 UnmarshalInto(&jsonResponse)
281−
282277 if err != nil {
283− return
278+ return err
284279 }
285280
286− now := time.Now()
287− expires := time.Unix(jsonResponse.ExpiresOn, 0)
288281 s.AccessToken = jsonResponse.AccessToken
289282 s.IDToken = jsonResponse.IDToken
290283 s.RefreshToken = jsonResponse.RefreshToken
291− s.CreatedAt = &now
292− s.ExpiresOn = &expires
284+
285+ s.CreatedAtNow()
286+ s.SetExpiresOn(time.Unix(jsonResponse.ExpiresOn, 0))
293287
294288 email, err := p.verifyTokenAndExtractEmail(ctx, s.IDToken)
295289
@@ -312,7 +306,7 @@ func (p *AzureProvider) redeemRefreshToken(ctx context.Context, s *sessions.Sess
312306 }
313307 }
314308
315− return
309+ return nil
316310}
317311
318312func makeAzureHeader(accessToken string) http.Header {
providers/gitlab.go+7−5
@@ -259,14 +259,16 @@ func (p *GitLabProvider) createSession(ctx context.Context, token *oauth2.Token)
259259 }
260260 }
261261
262− created := time.Now()
263− return &sessions.SessionState{
262+ ss := &sessions.SessionState{
264263 AccessToken: token.AccessToken,
265264 IDToken: getIDToken(token),
266265 RefreshToken: token.RefreshToken,
267− CreatedAt: &created,
268− ExpiresOn: &idToken.Expiry,
269− }, nil
266+ }
267+
268+ ss.CreatedAtNow()
269+ ss.SetExpiresOn(idToken.Expiry)
270+
271+ return ss, nil
270272}
271273
272274// ValidateSession checks that the session's IDToken is still valid
providers/google.go+12−13
@@ -163,23 +163,22 @@ func (p *GoogleProvider) Redeem(ctx context.Context, redirectURL, code string) (
163163 return nil, err
164164 }
165165
166− created := time.Now()
167− expires := time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second)
168−
169− return &sessions.SessionState{
166+ ss := &sessions.SessionState{
170167 AccessToken: jsonResponse.AccessToken,
171168 IDToken: jsonResponse.IDToken,
172− CreatedAt: &created,
173− ExpiresOn: &expires,
174169 RefreshToken: jsonResponse.RefreshToken,
175170 Email: c.Email,
176171 User: c.Subject,
177− }, nil
172+ }
173+ ss.CreatedAtNow()
174+ ss.ExpiresIn(time.Duration(jsonResponse.ExpiresIn) * time.Second)
175+
176+ return ss, nil
178177}
179178
180179// EnrichSession checks the listed Google Groups configured and adds any
181180// that the user is a member of to session.Groups.
182−func (p *GoogleProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
181+func (p *GoogleProvider) EnrichSession(_ context.Context, s *sessions.SessionState) error {
183182 // TODO (@NickMeves) - Move to pure EnrichSession logic and stop
184183 // reusing legacy `groupValidator`.
185184 //
@@ -272,7 +271,7 @@ func (p *GoogleProvider) RefreshSession(ctx context.Context, s *sessions.Session
272271 return false, nil
273272 }
274273
275− newToken, newIDToken, duration, err := p.redeemRefreshToken(ctx, s.RefreshToken)
274+ newToken, newIDToken, ttl, err := p.redeemRefreshToken(ctx, s.RefreshToken)
276275 if err != nil {
277276 return false, err
278277 }
@@ -285,12 +284,12 @@ func (p *GoogleProvider) RefreshSession(ctx context.Context, s *sessions.Session
285284 return false, fmt.Errorf("%s is no longer in the group(s)", s.Email)
286285 }
287286
288− origExpiration := s.ExpiresOn
289− expires := time.Now().Add(duration).Truncate(time.Second)
290287 s.AccessToken = newToken
291288 s.IDToken = newIDToken
292− s.ExpiresOn = &expires
293− logger.Printf("refreshed access token %s (expired on %s)", s, origExpiration)
289+
290+ s.CreatedAtNow()
291+ s.ExpiresIn(ttl)
292+
294293 return true, nil
295294}
296295
providers/logingov.go+8−9
@@ -159,7 +159,7 @@ func emailFromUserInfo(ctx context.Context, accessToken string, userInfoEndpoint
159159}
160160
161161// Redeem exchanges the OAuth2 authentication token for an ID token
162−func (p *LoginGovProvider) Redeem(ctx context.Context, redirectURL, code string) (*sessions.SessionState, error) {
162+func (p *LoginGovProvider) Redeem(ctx context.Context, _, code string) (*sessions.SessionState, error) {
163163 if code == "" {
164164 return nil, ErrMissingCode
165165 }
@@ -214,17 +214,16 @@ func (p *LoginGovProvider) Redeem(ctx context.Context, redirectURL, code string)
214214 return nil, err
215215 }
216216
217− created := time.Now()
218− expires := time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second)
219−
220− // Store the data that we found in the session state
221− return &sessions.SessionState{
217+ session := &sessions.SessionState{
222218 AccessToken: jsonResponse.AccessToken,
223219 IDToken: jsonResponse.IDToken,
224− CreatedAt: &created,
225− ExpiresOn: &expires,
226220 Email: email,
227− }, nil
221+ }
222+
223+ session.CreatedAtNow()
224+ session.ExpiresIn(time.Duration(jsonResponse.ExpiresIn) * time.Second)
225+
226+ return session, nil
228227}
229228
230229// GetLoginURL overrides GetLoginURL to add login.gov parameters
providers/oidc.go+5−4
@@ -226,7 +226,9 @@ func (p *OIDCProvider) CreateSessionFromToken(ctx context.Context, token string)
226226 ss.AccessToken = token
227227 ss.IDToken = token
228228 ss.RefreshToken = ""
229− ss.ExpiresOn = &idToken.Expiry
229+
230+ ss.CreatedAtNow()
231+ ss.SetExpiresOn(idToken.Expiry)
230232
231233 return ss, nil
232234}
@@ -256,9 +258,8 @@ func (p *OIDCProvider) createSession(ctx context.Context, token *oauth2.Token, r
256258 ss.RefreshToken = token.RefreshToken
257259 ss.IDToken = getIDToken(token)
258260
259− created := time.Now()
260− ss.CreatedAt = &created
261− ss.ExpiresOn = &token.Expiry
261+ ss.CreatedAtNow()
262+ ss.SetExpiresOn(token.Expiry)
262263
263264 return ss, nil
264265}
providers/provider_default.go+6−3
@@ -6,7 +6,6 @@ import (
66 "errors"
77 "fmt"
88 "net/url"
9− "time"
109
1110 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1211 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
@@ -85,9 +84,13 @@ func (p *ProviderData) Redeem(ctx context.Context, redirectURL, code string) (*s
8584 if err != nil {
8685 return nil, err
8786 }
87+ // TODO (@NickMeves): Uses OAuth `expires_in` to set an expiration
8888 if token := values.Get("access_token"); token != "" {
89− created := time.Now()
90− return &sessions.SessionState{AccessToken: token, CreatedAt: &created}, nil
89+ ss := &sessions.SessionState{
90+ AccessToken: token,
91+ }
92+ ss.CreatedAtNow()
93+ return ss, nil
9194 }
9295
9396 return nil, fmt.Errorf("no access token found %s", result.Body())