mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-03-23 21:50:48 +02:00
Manage session time fields centrally
This commit is contained in:
parent
7e80e5596b
commit
7fa6d2d024
@ -786,6 +786,15 @@ func (p *OAuthProxy) redeemCode(req *http.Request) (*sessionsapi.SessionState, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force setting these in case the Provider didn't
|
||||||
|
if s.CreatedAt == nil {
|
||||||
|
s.CreatedAtNow()
|
||||||
|
}
|
||||||
|
if s.ExpiresOn == nil {
|
||||||
|
s.ExpiresIn(p.CookieOptions.Expire)
|
||||||
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -861,9 +870,9 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
// See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en
|
// See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en
|
||||||
var noCacheHeaders = map[string]string{
|
var noCacheHeaders = map[string]string{
|
||||||
"Expires": time.Unix(0, 0).Format(time.RFC1123),
|
"Expires": time.Unix(0, 0).Format(time.RFC1123),
|
||||||
"Cache-Control": "no-cache, no-store, must-revalidate, max-age=0",
|
"Cache-Control": "no-cache, no-store, must-revalidate, max-age=0",
|
||||||
"X-Accel-Expires": "0", // https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/
|
"X-Accel-Expire": "0", // https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareNoCache prepares headers for preventing browser caching.
|
// prepareNoCache prepares headers for preventing browser caching.
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/clock"
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
|
||||||
"github.com/pierrec/lz4"
|
"github.com/pierrec/lz4"
|
||||||
"github.com/vmihailenco/msgpack/v4"
|
"github.com/vmihailenco/msgpack/v4"
|
||||||
@ -32,7 +33,8 @@ type SessionState struct {
|
|||||||
Groups []string `msgpack:"g,omitempty"`
|
Groups []string `msgpack:"g,omitempty"`
|
||||||
PreferredUsername string `msgpack:"pu,omitempty"`
|
PreferredUsername string `msgpack:"pu,omitempty"`
|
||||||
|
|
||||||
Lock Lock `msgpack:"-"`
|
Clock clock.Clock `msgpack:"-"`
|
||||||
|
Lock Lock `msgpack:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionState) ObtainLock(ctx context.Context, expiration time.Duration) error {
|
func (s *SessionState) ObtainLock(ctx context.Context, expiration time.Duration) error {
|
||||||
@ -63,9 +65,30 @@ func (s *SessionState) PeekLock(ctx context.Context) (bool, error) {
|
|||||||
return s.Lock.Peek(ctx)
|
return s.Lock.Peek(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreatedAtNow sets a SessionState's CreatedAt to now
|
||||||
|
func (s *SessionState) CreatedAtNow() {
|
||||||
|
now := s.Clock.Now()
|
||||||
|
s.CreatedAt = &now
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExpiresOn sets an expiration
|
||||||
|
func (s *SessionState) SetExpiresOn(exp time.Time) {
|
||||||
|
s.ExpiresOn = &exp
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpiresIn sets an expiration a certain duration from CreatedAt.
|
||||||
|
// CreatedAt will be set to time.Now if it is unset.
|
||||||
|
func (s *SessionState) ExpiresIn(d time.Duration) {
|
||||||
|
if s.CreatedAt == nil {
|
||||||
|
s.CreatedAtNow()
|
||||||
|
}
|
||||||
|
exp := s.CreatedAt.Add(d)
|
||||||
|
s.ExpiresOn = &exp
|
||||||
|
}
|
||||||
|
|
||||||
// IsExpired checks whether the session has expired
|
// IsExpired checks whether the session has expired
|
||||||
func (s *SessionState) IsExpired() bool {
|
func (s *SessionState) IsExpired() bool {
|
||||||
if s.ExpiresOn != nil && !s.ExpiresOn.IsZero() && s.ExpiresOn.Before(time.Now()) {
|
if s.ExpiresOn != nil && !s.ExpiresOn.IsZero() && s.ExpiresOn.Before(s.Clock.Now()) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -74,7 +97,7 @@ func (s *SessionState) IsExpired() bool {
|
|||||||
// Age returns the age of a session
|
// Age returns the age of a session
|
||||||
func (s *SessionState) Age() time.Duration {
|
func (s *SessionState) Age() time.Duration {
|
||||||
if s.CreatedAt != nil && !s.CreatedAt.IsZero() {
|
if s.CreatedAt != nil && !s.CreatedAt.IsZero() {
|
||||||
return time.Now().Truncate(time.Second).Sub(*s.CreatedAt)
|
return s.Clock.Now().Truncate(time.Second).Sub(*s.CreatedAt)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -142,8 +142,7 @@ func (s *storedSessionLoader) refreshSession(rw http.ResponseWriter, req *http.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we refreshed, update the `CreatedAt` time to reset the refresh timer
|
// If we refreshed, update the `CreatedAt` time to reset the refresh timer
|
||||||
// TODO: Implement
|
session.CreatedAtNow()
|
||||||
// session.CreatedAtNow()
|
|
||||||
|
|
||||||
// Because the session was refreshed, make sure to save it
|
// Because the session was refreshed, make sure to save it
|
||||||
err = s.store.Save(rw, req, session)
|
err = s.store.Save(rw, req, session)
|
||||||
|
@ -36,8 +36,7 @@ type SessionStore struct {
|
|||||||
// within Cookies set on the HTTP response writer
|
// within Cookies set on the HTTP response writer
|
||||||
func (s *SessionStore) Save(rw http.ResponseWriter, req *http.Request, ss *sessions.SessionState) error {
|
func (s *SessionStore) Save(rw http.ResponseWriter, req *http.Request, ss *sessions.SessionState) error {
|
||||||
if ss.CreatedAt == nil || ss.CreatedAt.IsZero() {
|
if ss.CreatedAt == nil || ss.CreatedAt.IsZero() {
|
||||||
now := time.Now()
|
ss.CreatedAtNow()
|
||||||
ss.CreatedAt = &now
|
|
||||||
}
|
}
|
||||||
value, err := s.cookieForSession(ss)
|
value, err := s.cookieForSession(ss)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -30,8 +30,7 @@ func NewManager(store Store, cookieOpts *options.Cookie) *Manager {
|
|||||||
// from the persistent data store.
|
// from the persistent data store.
|
||||||
func (m *Manager) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error {
|
func (m *Manager) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error {
|
||||||
if s.CreatedAt == nil || s.CreatedAt.IsZero() {
|
if s.CreatedAt == nil || s.CreatedAt.IsZero() {
|
||||||
now := time.Now()
|
s.CreatedAtNow()
|
||||||
s.CreatedAt = &now
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tckt, err := decodeTicketFromRequest(req, m.Options)
|
tckt, err := decodeTicketFromRequest(req, m.Options)
|
||||||
|
@ -142,16 +142,13 @@ func (p *AzureProvider) Redeem(ctx context.Context, redirectURL, code string) (*
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
created := time.Now()
|
|
||||||
expires := time.Unix(jsonResponse.ExpiresOn, 0)
|
|
||||||
|
|
||||||
session := &sessions.SessionState{
|
session := &sessions.SessionState{
|
||||||
AccessToken: jsonResponse.AccessToken,
|
AccessToken: jsonResponse.AccessToken,
|
||||||
IDToken: jsonResponse.IDToken,
|
IDToken: jsonResponse.IDToken,
|
||||||
CreatedAt: &created,
|
|
||||||
ExpiresOn: &expires,
|
|
||||||
RefreshToken: jsonResponse.RefreshToken,
|
RefreshToken: jsonResponse.RefreshToken,
|
||||||
}
|
}
|
||||||
|
session.CreatedAtNow()
|
||||||
|
session.SetExpiresOn(time.Unix(jsonResponse.ExpiresOn, 0))
|
||||||
|
|
||||||
email, err := p.verifyTokenAndExtractEmail(ctx, session.IDToken)
|
email, err := p.verifyTokenAndExtractEmail(ctx, session.IDToken)
|
||||||
|
|
||||||
@ -239,10 +236,9 @@ func (p *AzureProvider) verifyTokenAndExtractEmail(ctx context.Context, token st
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefreshSessionIfNeeded checks if the session has expired and uses the
|
// RefreshSession uses the RefreshToken to fetch new Access and ID Tokens
|
||||||
// RefreshToken to fetch a new ID token if required
|
func (p *AzureProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
|
||||||
func (p *AzureProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) {
|
if s == nil || s.RefreshToken == "" {
|
||||||
if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +253,7 @@ func (p *AzureProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *AzureProvider) redeemRefreshToken(ctx context.Context, s *sessions.SessionState) (err error) {
|
func (p *AzureProvider) redeemRefreshToken(ctx context.Context, s *sessions.SessionState) error {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Add("client_id", p.ClientID)
|
params.Add("client_id", p.ClientID)
|
||||||
params.Add("client_secret", p.ClientSecret)
|
params.Add("client_secret", p.ClientSecret)
|
||||||
@ -271,25 +267,23 @@ func (p *AzureProvider) redeemRefreshToken(ctx context.Context, s *sessions.Sess
|
|||||||
IDToken string `json:"id_token"`
|
IDToken string `json:"id_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
err = requests.New(p.RedeemURL.String()).
|
err := requests.New(p.RedeemURL.String()).
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
WithMethod("POST").
|
WithMethod("POST").
|
||||||
WithBody(bytes.NewBufferString(params.Encode())).
|
WithBody(bytes.NewBufferString(params.Encode())).
|
||||||
SetHeader("Content-Type", "application/x-www-form-urlencoded").
|
SetHeader("Content-Type", "application/x-www-form-urlencoded").
|
||||||
Do().
|
Do().
|
||||||
UnmarshalInto(&jsonResponse)
|
UnmarshalInto(&jsonResponse)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
expires := time.Unix(jsonResponse.ExpiresOn, 0)
|
|
||||||
s.AccessToken = jsonResponse.AccessToken
|
s.AccessToken = jsonResponse.AccessToken
|
||||||
s.IDToken = jsonResponse.IDToken
|
s.IDToken = jsonResponse.IDToken
|
||||||
s.RefreshToken = jsonResponse.RefreshToken
|
s.RefreshToken = jsonResponse.RefreshToken
|
||||||
s.CreatedAt = &now
|
|
||||||
s.ExpiresOn = &expires
|
s.CreatedAtNow()
|
||||||
|
s.SetExpiresOn(time.Unix(jsonResponse.ExpiresOn, 0))
|
||||||
|
|
||||||
email, err := p.verifyTokenAndExtractEmail(ctx, s.IDToken)
|
email, err := p.verifyTokenAndExtractEmail(ctx, s.IDToken)
|
||||||
|
|
||||||
@ -312,7 +306,7 @@ func (p *AzureProvider) redeemRefreshToken(ctx context.Context, s *sessions.Sess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeAzureHeader(accessToken string) http.Header {
|
func makeAzureHeader(accessToken string) http.Header {
|
||||||
|
@ -259,14 +259,16 @@ func (p *GitLabProvider) createSession(ctx context.Context, token *oauth2.Token)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
created := time.Now()
|
ss := &sessions.SessionState{
|
||||||
return &sessions.SessionState{
|
|
||||||
AccessToken: token.AccessToken,
|
AccessToken: token.AccessToken,
|
||||||
IDToken: getIDToken(token),
|
IDToken: getIDToken(token),
|
||||||
RefreshToken: token.RefreshToken,
|
RefreshToken: token.RefreshToken,
|
||||||
CreatedAt: &created,
|
}
|
||||||
ExpiresOn: &idToken.Expiry,
|
|
||||||
}, nil
|
ss.CreatedAtNow()
|
||||||
|
ss.SetExpiresOn(idToken.Expiry)
|
||||||
|
|
||||||
|
return ss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateSession checks that the session's IDToken is still valid
|
// ValidateSession checks that the session's IDToken is still valid
|
||||||
|
@ -163,23 +163,22 @@ func (p *GoogleProvider) Redeem(ctx context.Context, redirectURL, code string) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
created := time.Now()
|
ss := &sessions.SessionState{
|
||||||
expires := time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second)
|
|
||||||
|
|
||||||
return &sessions.SessionState{
|
|
||||||
AccessToken: jsonResponse.AccessToken,
|
AccessToken: jsonResponse.AccessToken,
|
||||||
IDToken: jsonResponse.IDToken,
|
IDToken: jsonResponse.IDToken,
|
||||||
CreatedAt: &created,
|
|
||||||
ExpiresOn: &expires,
|
|
||||||
RefreshToken: jsonResponse.RefreshToken,
|
RefreshToken: jsonResponse.RefreshToken,
|
||||||
Email: c.Email,
|
Email: c.Email,
|
||||||
User: c.Subject,
|
User: c.Subject,
|
||||||
}, nil
|
}
|
||||||
|
ss.CreatedAtNow()
|
||||||
|
ss.ExpiresIn(time.Duration(jsonResponse.ExpiresIn) * time.Second)
|
||||||
|
|
||||||
|
return ss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnrichSession checks the listed Google Groups configured and adds any
|
// EnrichSession checks the listed Google Groups configured and adds any
|
||||||
// that the user is a member of to session.Groups.
|
// that the user is a member of to session.Groups.
|
||||||
func (p *GoogleProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
|
func (p *GoogleProvider) EnrichSession(_ context.Context, s *sessions.SessionState) error {
|
||||||
// TODO (@NickMeves) - Move to pure EnrichSession logic and stop
|
// TODO (@NickMeves) - Move to pure EnrichSession logic and stop
|
||||||
// reusing legacy `groupValidator`.
|
// reusing legacy `groupValidator`.
|
||||||
//
|
//
|
||||||
@ -272,7 +271,7 @@ func (p *GoogleProvider) RefreshSession(ctx context.Context, s *sessions.Session
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
newToken, newIDToken, duration, err := p.redeemRefreshToken(ctx, s.RefreshToken)
|
newToken, newIDToken, ttl, err := p.redeemRefreshToken(ctx, s.RefreshToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -285,12 +284,12 @@ func (p *GoogleProvider) RefreshSession(ctx context.Context, s *sessions.Session
|
|||||||
return false, fmt.Errorf("%s is no longer in the group(s)", s.Email)
|
return false, fmt.Errorf("%s is no longer in the group(s)", s.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
origExpiration := s.ExpiresOn
|
|
||||||
expires := time.Now().Add(duration).Truncate(time.Second)
|
|
||||||
s.AccessToken = newToken
|
s.AccessToken = newToken
|
||||||
s.IDToken = newIDToken
|
s.IDToken = newIDToken
|
||||||
s.ExpiresOn = &expires
|
|
||||||
logger.Printf("refreshed access token %s (expired on %s)", s, origExpiration)
|
s.CreatedAtNow()
|
||||||
|
s.ExpiresIn(ttl)
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ func emailFromUserInfo(ctx context.Context, accessToken string, userInfoEndpoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Redeem exchanges the OAuth2 authentication token for an ID token
|
// Redeem exchanges the OAuth2 authentication token for an ID token
|
||||||
func (p *LoginGovProvider) Redeem(ctx context.Context, redirectURL, code string) (*sessions.SessionState, error) {
|
func (p *LoginGovProvider) Redeem(ctx context.Context, _, code string) (*sessions.SessionState, error) {
|
||||||
if code == "" {
|
if code == "" {
|
||||||
return nil, ErrMissingCode
|
return nil, ErrMissingCode
|
||||||
}
|
}
|
||||||
@ -214,17 +214,16 @@ func (p *LoginGovProvider) Redeem(ctx context.Context, redirectURL, code string)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
created := time.Now()
|
session := &sessions.SessionState{
|
||||||
expires := time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second)
|
|
||||||
|
|
||||||
// Store the data that we found in the session state
|
|
||||||
return &sessions.SessionState{
|
|
||||||
AccessToken: jsonResponse.AccessToken,
|
AccessToken: jsonResponse.AccessToken,
|
||||||
IDToken: jsonResponse.IDToken,
|
IDToken: jsonResponse.IDToken,
|
||||||
CreatedAt: &created,
|
|
||||||
ExpiresOn: &expires,
|
|
||||||
Email: email,
|
Email: email,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
session.CreatedAtNow()
|
||||||
|
session.ExpiresIn(time.Duration(jsonResponse.ExpiresIn) * time.Second)
|
||||||
|
|
||||||
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLoginURL overrides GetLoginURL to add login.gov parameters
|
// GetLoginURL overrides GetLoginURL to add login.gov parameters
|
||||||
|
@ -226,7 +226,9 @@ func (p *OIDCProvider) CreateSessionFromToken(ctx context.Context, token string)
|
|||||||
ss.AccessToken = token
|
ss.AccessToken = token
|
||||||
ss.IDToken = token
|
ss.IDToken = token
|
||||||
ss.RefreshToken = ""
|
ss.RefreshToken = ""
|
||||||
ss.ExpiresOn = &idToken.Expiry
|
|
||||||
|
ss.CreatedAtNow()
|
||||||
|
ss.SetExpiresOn(idToken.Expiry)
|
||||||
|
|
||||||
return ss, nil
|
return ss, nil
|
||||||
}
|
}
|
||||||
@ -256,9 +258,8 @@ func (p *OIDCProvider) createSession(ctx context.Context, token *oauth2.Token, r
|
|||||||
ss.RefreshToken = token.RefreshToken
|
ss.RefreshToken = token.RefreshToken
|
||||||
ss.IDToken = getIDToken(token)
|
ss.IDToken = getIDToken(token)
|
||||||
|
|
||||||
created := time.Now()
|
ss.CreatedAtNow()
|
||||||
ss.CreatedAt = &created
|
ss.SetExpiresOn(token.Expiry)
|
||||||
ss.ExpiresOn = &token.Expiry
|
|
||||||
|
|
||||||
return ss, nil
|
return ss, nil
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
"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
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// TODO (@NickMeves): Uses OAuth `expires_in` to set an expiration
|
||||||
if token := values.Get("access_token"); token != "" {
|
if token := values.Get("access_token"); token != "" {
|
||||||
created := time.Now()
|
ss := &sessions.SessionState{
|
||||||
return &sessions.SessionState{AccessToken: token, CreatedAt: &created}, nil
|
AccessToken: token,
|
||||||
|
}
|
||||||
|
ss.CreatedAtNow()
|
||||||
|
return ss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("no access token found %s", result.Body())
|
return nil, fmt.Errorf("no access token found %s", result.Body())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user