feat: refresh token on demand
Co-Authored-By: Chadwick Baatz <40895835+cmbaatz@users.noreply.github.com> Co-Authored-By: Jan Larwig <jan@larwig.com> Signed-off-by: Jan Larwig <jan@larwig.com>
This commit is contained in:
parent
0e1dc9bb84
commit
b0ae034d37
|
|
@ -12,6 +12,7 @@
|
||||||
- [#3116](https://github.com/oauth2-proxy/oauth2-proxy/pull/3116) feat: bump to go1.24.5 and full dependency update (@wardviaene / @dolmen)
|
- [#3116](https://github.com/oauth2-proxy/oauth2-proxy/pull/3116) feat: bump to go1.24.5 and full dependency update (@wardviaene / @dolmen)
|
||||||
- [#3097](https://github.com/oauth2-proxy/oauth2-proxy/pull/3097) chore(deps): update alpine base image to v3.22.0
|
- [#3097](https://github.com/oauth2-proxy/oauth2-proxy/pull/3097) chore(deps): update alpine base image to v3.22.0
|
||||||
- [#3101](https://github.com/oauth2-proxy/oauth2-proxy/pull/3101) fix: return error for empty Redis URL list (@dgivens)
|
- [#3101](https://github.com/oauth2-proxy/oauth2-proxy/pull/3101) fix: return error for empty Redis URL list (@dgivens)
|
||||||
|
- [#2019](https://github.com/oauth2-proxy/oauth2-proxy/issues/2019) feat: refresh token on demand (@cmbaatz)
|
||||||
|
|
||||||
# V7.9.0
|
# V7.9.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ const (
|
||||||
oauthCallbackPath = "/callback"
|
oauthCallbackPath = "/callback"
|
||||||
authOnlyPath = "/auth"
|
authOnlyPath = "/auth"
|
||||||
userInfoPath = "/userinfo"
|
userInfoPath = "/userinfo"
|
||||||
|
refreshPath = "/refresh"
|
||||||
staticPathPrefix = "/static/"
|
staticPathPrefix = "/static/"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@ type OAuthProxy struct {
|
||||||
trustedIPs *ip.NetSet
|
trustedIPs *ip.NetSet
|
||||||
|
|
||||||
sessionChain alice.Chain
|
sessionChain alice.Chain
|
||||||
|
refreshChain alice.Chain
|
||||||
headersChain alice.Chain
|
headersChain alice.Chain
|
||||||
preAuthChain alice.Chain
|
preAuthChain alice.Chain
|
||||||
pageWriter pagewriter.Writer
|
pageWriter pagewriter.Writer
|
||||||
|
|
@ -204,7 +206,21 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not build pre-auth chain: %v", err)
|
return nil, fmt.Errorf("could not build pre-auth chain: %v", err)
|
||||||
}
|
}
|
||||||
sessionChain := buildSessionChain(opts, provider, sessionStore, basicAuthValidator)
|
sessionChain := buildSessionChain(opts, provider, sessionStore, basicAuthValidator).Append(
|
||||||
|
middleware.NewStoredSessionLoader(&middleware.StoredSessionLoaderOptions{
|
||||||
|
SessionStore: sessionStore,
|
||||||
|
RefreshPeriod: opts.Cookie.Refresh,
|
||||||
|
RefreshSession: provider.RefreshSession,
|
||||||
|
ValidateSession: provider.ValidateSession,
|
||||||
|
}))
|
||||||
|
refreshChain := buildSessionChain(opts, provider, basicAuthValidator).Append(
|
||||||
|
middleware.NewStoredSessionRefresher(&middleware.StoredSessionLoaderOptions{
|
||||||
|
SessionStore: sessionStore,
|
||||||
|
RefreshPeriod: opts.Cookie.Refresh,
|
||||||
|
RefreshSession: provider.RefreshSession,
|
||||||
|
ValidateSession: provider.ValidateSession,
|
||||||
|
}))
|
||||||
|
|
||||||
headersChain, err := buildHeadersChain(opts)
|
headersChain, err := buildHeadersChain(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not build headers chain: %v", err)
|
return nil, fmt.Errorf("could not build headers chain: %v", err)
|
||||||
|
|
@ -241,6 +257,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
||||||
basicAuthValidator: basicAuthValidator,
|
basicAuthValidator: basicAuthValidator,
|
||||||
basicAuthGroups: opts.HtpasswdUserGroups,
|
basicAuthGroups: opts.HtpasswdUserGroups,
|
||||||
sessionChain: sessionChain,
|
sessionChain: sessionChain,
|
||||||
|
refreshChain: refreshChain,
|
||||||
headersChain: headersChain,
|
headersChain: headersChain,
|
||||||
preAuthChain: preAuthChain,
|
preAuthChain: preAuthChain,
|
||||||
pageWriter: pageWriter,
|
pageWriter: pageWriter,
|
||||||
|
|
@ -347,6 +364,7 @@ func (p *OAuthProxy) buildProxySubrouter(s *mux.Router) {
|
||||||
|
|
||||||
// The userinfo and logout endpoints needs to load sessions before handling the request
|
// The userinfo and logout endpoints needs to load sessions before handling the request
|
||||||
s.Path(userInfoPath).Handler(p.sessionChain.ThenFunc(p.UserInfo))
|
s.Path(userInfoPath).Handler(p.sessionChain.ThenFunc(p.UserInfo))
|
||||||
|
s.Path(refreshPath).Handler(p.refreshChain.ThenFunc(p.Proxy))
|
||||||
s.Path(signOutPath).Handler(p.sessionChain.ThenFunc(p.SignOut))
|
s.Path(signOutPath).Handler(p.sessionChain.ThenFunc(p.SignOut))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2006,6 +2006,10 @@ func Test_noCacheHeaders(t *testing.T) {
|
||||||
path: "/oauth2/userinfo",
|
path: "/oauth2/userinfo",
|
||||||
hasNoCache: true,
|
hasNoCache: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/oauth2/refresh",
|
||||||
|
hasNoCache: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/upstream",
|
path: "/upstream",
|
||||||
hasNoCache: false,
|
hasNoCache: false,
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,20 @@ func NewStoredSessionLoader(opts *StoredSessionLoaderOptions) alice.Constructor
|
||||||
return ss.loadSession
|
return ss.loadSession
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewStoredSessionRefresher creates a new storedSessionLoader which allows for the
|
||||||
|
// refresh of sessions from the session store on demand.
|
||||||
|
// If no session is found, the request will be passed to the nex handler.
|
||||||
|
// If a session was loader by a previous handler, it will not be replaced.
|
||||||
|
func NewStoredSessionRefresher(opts *StoredSessionLoaderOptions) alice.Constructor {
|
||||||
|
sr := &storedSessionLoader{
|
||||||
|
store: opts.SessionStore,
|
||||||
|
refreshPeriod: opts.RefreshPeriod,
|
||||||
|
sessionRefresher: opts.RefreshSession,
|
||||||
|
sessionValidator: opts.ValidateSession,
|
||||||
|
}
|
||||||
|
return sr.forceRefreshSession
|
||||||
|
}
|
||||||
|
|
||||||
// storedSessionLoader is responsible for loading sessions from cookie
|
// storedSessionLoader is responsible for loading sessions from cookie
|
||||||
// identified sessions in the session store.
|
// identified sessions in the session store.
|
||||||
type storedSessionLoader struct {
|
type storedSessionLoader struct {
|
||||||
|
|
@ -113,7 +127,7 @@ func (s *storedSessionLoader) getValidatedSession(rw http.ResponseWriter, req *h
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.refreshSessionIfNeeded(rw, req, session)
|
err = s.refreshSessionIfNeeded(rw, req, session, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error refreshing access token for session (%s): %v", session, err)
|
return nil, fmt.Errorf("error refreshing access token for session (%s): %v", session, err)
|
||||||
}
|
}
|
||||||
|
|
@ -124,8 +138,8 @@ func (s *storedSessionLoader) getValidatedSession(rw http.ResponseWriter, req *h
|
||||||
// refreshSessionIfNeeded will attempt to refresh a session if the session
|
// refreshSessionIfNeeded will attempt to refresh a session if the session
|
||||||
// is older than the refresh period.
|
// is older than the refresh period.
|
||||||
// Success or fail, we will then validate the session.
|
// Success or fail, we will then validate the session.
|
||||||
func (s *storedSessionLoader) refreshSessionIfNeeded(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) error {
|
func (s *storedSessionLoader) refreshSessionIfNeeded(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState, forceRefresh bool) error {
|
||||||
if !needsRefresh(s.refreshPeriod, session) {
|
if !forceRefresh && !needsRefresh(s.refreshPeriod, session) {
|
||||||
// Refresh is disabled or the session is not old enough, do nothing
|
// Refresh is disabled or the session is not old enough, do nothing
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +193,7 @@ func (s *storedSessionLoader) refreshSessionIfNeeded(rw http.ResponseWriter, req
|
||||||
// Loading from the session store creates a new lock in the session.
|
// Loading from the session store creates a new lock in the session.
|
||||||
session.Lock = lock
|
session.Lock = lock
|
||||||
|
|
||||||
if !needsRefresh(s.refreshPeriod, session) {
|
if !forceRefresh && !needsRefresh(s.refreshPeriod, session) {
|
||||||
// The session must have already been refreshed while we were waiting to
|
// The session must have already been refreshed while we were waiting to
|
||||||
// obtain the lock.
|
// obtain the lock.
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -252,3 +266,58 @@ func (s *storedSessionLoader) validateSession(ctx context.Context, session *sess
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// forceRefreshSession attempts to load a session as identified by the request cookies.
|
||||||
|
// If no session is found, the request will be passed to the next handler.
|
||||||
|
// If a session was loaded by a previous handler, it will not be refreshed.
|
||||||
|
func (s *storedSessionLoader) forceRefreshSession(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
scope := middlewareapi.GetRequestScope(req)
|
||||||
|
|
||||||
|
// If refreshPeriod isn't defined then forward request to the next handler
|
||||||
|
if s.refreshPeriod <= time.Duration(0) {
|
||||||
|
next.ServeHTTP(rw, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := s.store.Load(req)
|
||||||
|
if err != nil {
|
||||||
|
s.handleRefreshError(rw, req, next, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if session == nil {
|
||||||
|
next.ServeHTTP(rw, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.refreshSessionIfNeeded(rw, req, session, true)
|
||||||
|
if err != nil {
|
||||||
|
s.handleRefreshError(rw, req, next, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the session to the scope if it was found
|
||||||
|
scope.Session = session
|
||||||
|
next.ServeHTTP(rw, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *storedSessionLoader) handleRefreshError(rw http.ResponseWriter, req *http.Request, next http.Handler, err error) {
|
||||||
|
if errors.Is(err, http.ErrNoCookie) {
|
||||||
|
logger.Error("Error refreshing session: session cookie not present")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// In the case when there was an error loading the session,
|
||||||
|
// we should clear the session
|
||||||
|
logger.Errorf("Error loading cookied session: %v, removing session", err)
|
||||||
|
|
||||||
|
if err = s.store.Clear(rw, req); err != nil {
|
||||||
|
logger.Errorf("Error removing session: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := middlewareapi.GetRequestScope(req)
|
||||||
|
scope.Session = nil
|
||||||
|
next.ServeHTTP(rw, req)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ var _ = Describe("Stored Session Suite", func() {
|
||||||
const (
|
const (
|
||||||
refresh = "Refresh"
|
refresh = "Refresh"
|
||||||
refreshed = "Refreshed"
|
refreshed = "Refreshed"
|
||||||
|
forcedRefresh = "Forced-Refresh"
|
||||||
noRefresh = "NoRefresh"
|
noRefresh = "NoRefresh"
|
||||||
notImplemented = "NotImplemented"
|
notImplemented = "NotImplemented"
|
||||||
)
|
)
|
||||||
|
|
@ -94,13 +95,18 @@ var _ = Describe("Stored Session Suite", func() {
|
||||||
Context("StoredSessionLoader", func() {
|
Context("StoredSessionLoader", func() {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
createdPast := now.Add(-5 * time.Minute)
|
createdPast := now.Add(-5 * time.Minute)
|
||||||
|
recentPast := now.Add(-5 * time.Second)
|
||||||
createdFuture := now.Add(5 * time.Minute)
|
createdFuture := now.Add(5 * time.Minute)
|
||||||
|
recent := now.Add(1 * time.Minute)
|
||||||
|
|
||||||
var defaultRefreshFunc = func(_ context.Context, ss *sessionsapi.SessionState) (bool, error) {
|
var defaultRefreshFunc = func(_ context.Context, ss *sessionsapi.SessionState) (bool, error) {
|
||||||
switch ss.RefreshToken {
|
switch ss.RefreshToken {
|
||||||
case refresh:
|
case refresh:
|
||||||
ss.RefreshToken = refreshed
|
ss.RefreshToken = refreshed
|
||||||
return true, nil
|
return true, nil
|
||||||
|
case forcedRefresh:
|
||||||
|
ss.RefreshToken = refreshed
|
||||||
|
return true, nil
|
||||||
case noRefresh:
|
case noRefresh:
|
||||||
return false, nil
|
return false, nil
|
||||||
default:
|
default:
|
||||||
|
|
@ -146,6 +152,18 @@ var _ = Describe("Stored Session Suite", func() {
|
||||||
CreatedAt: &createdPast,
|
CreatedAt: &createdPast,
|
||||||
ExpiresOn: &createdFuture,
|
ExpiresOn: &createdFuture,
|
||||||
}, nil
|
}, nil
|
||||||
|
case "_oauth2_proxy=NewSession":
|
||||||
|
return &sessionsapi.SessionState{
|
||||||
|
RefreshToken: "Forced-Refresh", // used to inform test to allow refresh. Will be overwritten
|
||||||
|
CreatedAt: &recentPast,
|
||||||
|
ExpiresOn: &createdFuture,
|
||||||
|
}, nil
|
||||||
|
case "_oauth2_proxy=OldSession":
|
||||||
|
return &sessionsapi.SessionState{
|
||||||
|
RefreshToken: "Forced-Refresh", // used to inform test to allow refresh. Will be overwritten
|
||||||
|
CreatedAt: &createdPast,
|
||||||
|
ExpiresOn: &createdFuture,
|
||||||
|
}, nil
|
||||||
case "_oauth2_proxy=NonExistent":
|
case "_oauth2_proxy=NonExistent":
|
||||||
return nil, fmt.Errorf("invalid cookie")
|
return nil, fmt.Errorf("invalid cookie")
|
||||||
default:
|
default:
|
||||||
|
|
@ -155,7 +173,7 @@ var _ = Describe("Stored Session Suite", func() {
|
||||||
}
|
}
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
clock.Set(now)
|
clock.Set(recent)
|
||||||
})
|
})
|
||||||
|
|
||||||
AfterEach(func() {
|
AfterEach(func() {
|
||||||
|
|
@ -172,7 +190,7 @@ var _ = Describe("Stored Session Suite", func() {
|
||||||
validateSession func(context.Context, *sessionsapi.SessionState) bool
|
validateSession func(context.Context, *sessionsapi.SessionState) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
DescribeTable("when serving a request",
|
DescribeTable("when serving a loadSession request",
|
||||||
func(in storedSessionLoaderTableInput) {
|
func(in storedSessionLoaderTableInput) {
|
||||||
scope := &middlewareapi.RequestScope{
|
scope := &middlewareapi.RequestScope{
|
||||||
Session: in.existingSession,
|
Session: in.existingSession,
|
||||||
|
|
@ -286,7 +304,7 @@ var _ = Describe("Stored Session Suite", func() {
|
||||||
existingSession: nil,
|
existingSession: nil,
|
||||||
expectedSession: &sessionsapi.SessionState{
|
expectedSession: &sessionsapi.SessionState{
|
||||||
RefreshToken: "Refreshed",
|
RefreshToken: "Refreshed",
|
||||||
CreatedAt: &now,
|
CreatedAt: &recent,
|
||||||
ExpiresOn: &createdFuture,
|
ExpiresOn: &createdFuture,
|
||||||
Lock: &sessionsapi.NoOpLock{},
|
Lock: &sessionsapi.NoOpLock{},
|
||||||
},
|
},
|
||||||
|
|
@ -335,6 +353,183 @@ var _ = Describe("Stored Session Suite", func() {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DescribeTable("when service a forceRefresh request",
|
||||||
|
func(in storedSessionLoaderTableInput) {
|
||||||
|
scope := &middlewareapi.RequestScope{
|
||||||
|
Session: in.existingSession,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the request with the request header and a request scope
|
||||||
|
req := httptest.NewRequest("", "/", nil)
|
||||||
|
req.Header = in.requestHeaders
|
||||||
|
req = middlewareapi.AddRequestScope(req, scope)
|
||||||
|
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
|
||||||
|
opts := &StoredSessionLoaderOptions{
|
||||||
|
SessionStore: in.store,
|
||||||
|
RefreshPeriod: in.refreshPeriod,
|
||||||
|
RefreshSession: in.refreshSession,
|
||||||
|
ValidateSession: in.validateSession,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the handler with a next handler that will capture the session
|
||||||
|
// from the scope
|
||||||
|
var gotSession *sessionsapi.SessionState
|
||||||
|
handler := NewStoredSessionRefresher(opts)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
gotSession = middlewareapi.GetRequestScope(r).Session
|
||||||
|
}))
|
||||||
|
handler.ServeHTTP(rw, req)
|
||||||
|
|
||||||
|
Expect(gotSession).To(Equal(in.expectedSession))
|
||||||
|
},
|
||||||
|
Entry("with no cookie", storedSessionLoaderTableInput{
|
||||||
|
requestHeaders: http.Header{},
|
||||||
|
existingSession: nil,
|
||||||
|
expectedSession: nil,
|
||||||
|
store: defaultSessionStore,
|
||||||
|
refreshPeriod: 1 * time.Minute,
|
||||||
|
refreshSession: defaultRefreshFunc,
|
||||||
|
validateSession: defaultValidateFunc,
|
||||||
|
}),
|
||||||
|
Entry("with an invalid cookie", storedSessionLoaderTableInput{
|
||||||
|
requestHeaders: http.Header{
|
||||||
|
"Cookie": []string{"_oauth2_proxy=NonExistent"},
|
||||||
|
},
|
||||||
|
existingSession: nil,
|
||||||
|
expectedSession: nil,
|
||||||
|
store: defaultSessionStore,
|
||||||
|
refreshPeriod: 1 * time.Minute,
|
||||||
|
refreshSession: defaultRefreshFunc,
|
||||||
|
validateSession: defaultValidateFunc,
|
||||||
|
}),
|
||||||
|
Entry("with an existing session", storedSessionLoaderTableInput{
|
||||||
|
requestHeaders: http.Header{
|
||||||
|
"Cookie": []string{"_oauth2_proxy=NewSession"},
|
||||||
|
},
|
||||||
|
existingSession: &sessionsapi.SessionState{
|
||||||
|
RefreshToken: "Forced-Refresh",
|
||||||
|
CreatedAt: &recentPast,
|
||||||
|
},
|
||||||
|
expectedSession: &sessionsapi.SessionState{
|
||||||
|
RefreshToken: "Refreshed",
|
||||||
|
CreatedAt: &recent,
|
||||||
|
ExpiresOn: &createdFuture,
|
||||||
|
Lock: &sessionsapi.NoOpLock{},
|
||||||
|
},
|
||||||
|
store: defaultSessionStore,
|
||||||
|
refreshPeriod: 1 * time.Minute,
|
||||||
|
refreshSession: defaultRefreshFunc,
|
||||||
|
validateSession: defaultValidateFunc,
|
||||||
|
}),
|
||||||
|
Entry("with a session that has not expired and cannot be refreshed", storedSessionLoaderTableInput{
|
||||||
|
requestHeaders: http.Header{
|
||||||
|
"Cookie": []string{"_oauth2_proxy=NoRefreshSession"},
|
||||||
|
},
|
||||||
|
existingSession: nil,
|
||||||
|
expectedSession: &sessionsapi.SessionState{
|
||||||
|
RefreshToken: noRefresh,
|
||||||
|
CreatedAt: &createdPast,
|
||||||
|
ExpiresOn: &createdFuture,
|
||||||
|
Lock: &sessionsapi.NoOpLock{},
|
||||||
|
},
|
||||||
|
store: defaultSessionStore,
|
||||||
|
refreshPeriod: 1 * time.Minute,
|
||||||
|
refreshSession: defaultRefreshFunc,
|
||||||
|
validateSession: defaultValidateFunc,
|
||||||
|
}),
|
||||||
|
Entry("with a session that cannot refresh and has expired", storedSessionLoaderTableInput{
|
||||||
|
requestHeaders: http.Header{
|
||||||
|
"Cookie": []string{"_oauth2_proxy=ExpiredNoRefreshSession"},
|
||||||
|
},
|
||||||
|
existingSession: nil,
|
||||||
|
expectedSession: nil,
|
||||||
|
store: defaultSessionStore,
|
||||||
|
refreshPeriod: 1 * time.Minute,
|
||||||
|
refreshSession: defaultRefreshFunc,
|
||||||
|
validateSession: defaultValidateFunc,
|
||||||
|
}),
|
||||||
|
Entry("with a session that can refresh, but is younger than refresh period", storedSessionLoaderTableInput{
|
||||||
|
requestHeaders: http.Header{
|
||||||
|
"Cookie": []string{"_oauth2_proxy=NewSession"},
|
||||||
|
},
|
||||||
|
existingSession: nil,
|
||||||
|
expectedSession: &sessionsapi.SessionState{
|
||||||
|
RefreshToken: "Refreshed",
|
||||||
|
CreatedAt: &recent,
|
||||||
|
ExpiresOn: &createdFuture,
|
||||||
|
Lock: &sessionsapi.NoOpLock{},
|
||||||
|
},
|
||||||
|
store: defaultSessionStore,
|
||||||
|
refreshPeriod: 1 * time.Minute,
|
||||||
|
refreshSession: defaultRefreshFunc,
|
||||||
|
validateSession: defaultValidateFunc,
|
||||||
|
}),
|
||||||
|
Entry("with a session that can refresh and is older than the refresh period", storedSessionLoaderTableInput{
|
||||||
|
requestHeaders: http.Header{
|
||||||
|
"Cookie": []string{"_oauth2_proxy=OldSession"},
|
||||||
|
},
|
||||||
|
existingSession: nil,
|
||||||
|
expectedSession: &sessionsapi.SessionState{
|
||||||
|
RefreshToken: "Refreshed",
|
||||||
|
CreatedAt: &recent,
|
||||||
|
ExpiresOn: &createdFuture,
|
||||||
|
Lock: &sessionsapi.NoOpLock{},
|
||||||
|
},
|
||||||
|
store: defaultSessionStore,
|
||||||
|
refreshPeriod: 1 * time.Minute,
|
||||||
|
refreshSession: defaultRefreshFunc,
|
||||||
|
validateSession: defaultValidateFunc,
|
||||||
|
}),
|
||||||
|
Entry("when the provider refresh fails but validation succeeds", storedSessionLoaderTableInput{
|
||||||
|
requestHeaders: http.Header{
|
||||||
|
"Cookie": []string{"_oauth2_proxy=RefreshError"},
|
||||||
|
},
|
||||||
|
existingSession: nil,
|
||||||
|
expectedSession: &sessionsapi.SessionState{
|
||||||
|
RefreshToken: "RefreshError",
|
||||||
|
CreatedAt: &createdPast,
|
||||||
|
ExpiresOn: &createdFuture,
|
||||||
|
Lock: &sessionsapi.NoOpLock{},
|
||||||
|
},
|
||||||
|
store: defaultSessionStore,
|
||||||
|
refreshPeriod: 1 * time.Minute,
|
||||||
|
refreshSession: defaultRefreshFunc,
|
||||||
|
validateSession: defaultValidateFunc,
|
||||||
|
}),
|
||||||
|
Entry("when the provider refresh fails and validation fails", storedSessionLoaderTableInput{
|
||||||
|
requestHeaders: http.Header{
|
||||||
|
"Cookie": []string{"_oauth2_proxy=RefreshError"},
|
||||||
|
},
|
||||||
|
existingSession: nil,
|
||||||
|
expectedSession: nil,
|
||||||
|
store: defaultSessionStore,
|
||||||
|
refreshPeriod: 1 * time.Minute,
|
||||||
|
refreshSession: defaultRefreshFunc,
|
||||||
|
validateSession: func(context.Context, *sessionsapi.SessionState) bool { return false },
|
||||||
|
}),
|
||||||
|
Entry("when the session is not refreshed and is no longer valid", storedSessionLoaderTableInput{
|
||||||
|
requestHeaders: http.Header{
|
||||||
|
"Cookie": []string{"_oauth2_proxy=InvalidNoRefreshSession"},
|
||||||
|
},
|
||||||
|
existingSession: nil,
|
||||||
|
expectedSession: nil,
|
||||||
|
store: defaultSessionStore,
|
||||||
|
refreshPeriod: 1 * time.Minute,
|
||||||
|
refreshSession: defaultRefreshFunc,
|
||||||
|
validateSession: defaultValidateFunc,
|
||||||
|
}),
|
||||||
|
Entry("when refresh period is not defined", storedSessionLoaderTableInput{
|
||||||
|
requestHeaders: http.Header{
|
||||||
|
"Cookie": []string{"_oauth2_proxy=NewSession"},
|
||||||
|
},
|
||||||
|
existingSession: nil,
|
||||||
|
expectedSession: nil,
|
||||||
|
store: defaultSessionStore,
|
||||||
|
refreshPeriod: 0,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
type storedSessionLoaderConcurrentTableInput struct {
|
type storedSessionLoaderConcurrentTableInput struct {
|
||||||
existingSession *sessionsapi.SessionState
|
existingSession *sessionsapi.SessionState
|
||||||
refreshPeriod time.Duration
|
refreshPeriod time.Duration
|
||||||
|
|
@ -496,7 +691,7 @@ var _ = Describe("Stored Session Suite", func() {
|
||||||
}
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest("", "/", nil)
|
req := httptest.NewRequest("", "/", nil)
|
||||||
err := s.refreshSessionIfNeeded(nil, req, in.session)
|
err := s.refreshSessionIfNeeded(nil, req, in.session, false)
|
||||||
if in.expectedErr != nil {
|
if in.expectedErr != nil {
|
||||||
Expect(err).To(MatchError(in.expectedErr))
|
Expect(err).To(MatchError(in.expectedErr))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue