Skip to content

Commit 7ccc864

Browse files
committed
sso_*:clean up some validation calls
1 parent e49ca7a commit 7ccc864

File tree

7 files changed

+93
-107
lines changed

7 files changed

+93
-107
lines changed

internal/pkg/options/email_group_validator.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func (v EmailGroupValidator) Validate(session *sessions.SessionState) error {
4242
func (v EmailGroupValidator) validate(session *sessions.SessionState) error {
4343
matchedGroups, valid, err := v.Provider.ValidateGroup(session.Email, v.AllowedGroups, session.AccessToken)
4444
if err != nil {
45-
return ErrValidationError
45+
return err
4646
}
4747

4848
if valid {

internal/pkg/options/validators.go

-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ var (
1010
// These error message should be formatted in such a way that is appropriate
1111
// for display to the end user.
1212
ErrInvalidEmailAddress = errors.New("Invalid Email Address In Session State")
13-
ErrValidationError = errors.New("Error during validation")
1413
)
1514

1615
type Validator interface {

internal/pkg/sessions/session_state.go

+8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ func (s *SessionState) ValidationPeriodExpired() bool {
4545
return isExpired(s.ValidDeadline)
4646
}
4747

48+
// IsWithinGracePeriod returns true if the session is still within the grace period
49+
func (s *SessionState) IsWithinGracePeriod(gracePeriodTTL time.Duration) bool {
50+
if s.GracePeriodStart.IsZero() {
51+
s.GracePeriodStart = time.Now()
52+
}
53+
return s.GracePeriodStart.Add(gracePeriodTTL).After(time.Now())
54+
}
55+
4856
func isExpired(t time.Time) bool {
4957
if t.Before(time.Now()) {
5058
return true

internal/proxy/oauthproxy.go

+58-27
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, req *http.Request, code i
359359
p.templates.ExecuteTemplate(rw, "error.html", t)
360360
}
361361

362-
// IsWhitelistedRequest cheks that proxy host exists and checks the SkipAuthRegex
362+
// IsWhitelistedRequest checks that proxy host exists and checks the SkipAuthRegex
363363
func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) bool {
364364
if p.skipAuthPreflight && req.Method == "OPTIONS" {
365365
return true
@@ -375,6 +375,28 @@ func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) bool {
375375
return false
376376
}
377377

378+
// runValidatorsWithGracePeriod runs all validators and upon finding errors, checks to see if the
379+
// auth provider is explicity denying authentication or if it's merely unavailable. If it's unavailable,
380+
// we check whether the session is within the grace period or not to determine whether to allow the request
381+
// at this point.
382+
func (p *OAuthProxy) runValidatorsWithGracePeriod(session *sessions.SessionState) (err error) {
383+
logger := log.NewLogEntry()
384+
errors := options.RunValidators(p.Validators, session)
385+
if len(errors) == len(p.Validators) {
386+
for _, err := range errors {
387+
// Check to see if the auth provider is explicity denying authentication, or if it is merely unavailable.
388+
if err == providers.ErrAuthProviderUnavailable && session.IsWithinGracePeriod(p.provider.Data().GracePeriodTTL) {
389+
return err
390+
}
391+
}
392+
allowedGroups := p.upstreamConfig.AllowedGroups
393+
logger.WithUser(session.Email).WithAllowedGroups(allowedGroups).Info(
394+
"no longer authorized after validation period: %q", errors)
395+
return ErrUserNotAuthorized
396+
}
397+
return nil
398+
}
399+
378400
func (p *OAuthProxy) isXHR(req *http.Request) bool {
379401
return req.Header.Get("X-Requested-With") == "XMLHttpRequest"
380402
}
@@ -693,8 +715,6 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) (er
693715
remoteAddr := getRemoteAddr(req)
694716
tags := []string{"action:authenticate"}
695717

696-
allowedGroups := p.upstreamConfig.AllowedGroups
697-
698718
// Clear the session cookie if anything goes wrong.
699719
defer func() {
700720
if err != nil {
@@ -705,7 +725,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) (er
705725
session, err := p.sessionStore.LoadSession(req)
706726
if err != nil {
707727
// We loaded a cookie but it wasn't valid, clear it, and reject the request
708-
logger.Error(err, "error authenticating user")
728+
logger.Error(err, "invalid session loaded")
709729
return err
710730
}
711731

@@ -728,7 +748,23 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) (er
728748
} else if session.RefreshPeriodExpired() {
729749
// Refresh period is the period in which the access token is valid. This is ultimately
730750
// controlled by the upstream provider and tends to be around 1 hour.
731-
ok, err := p.provider.RefreshSession(session, allowedGroups)
751+
// If it has expired we:
752+
// - run email domain, email address, and email group validations against the session (if defined).
753+
// - attempt to refresh the session
754+
755+
err := p.runValidatorsWithGracePeriod(session)
756+
if err != nil {
757+
switch err {
758+
case providers.ErrAuthProviderUnavailable:
759+
tags = append(tags, "action:refresh_session", "error:validation_failed")
760+
p.StatsdClient.Incr("provider_error_fallback", tags, 1.0)
761+
session.RefreshDeadline = sessions.ExtendDeadline(p.provider.Data().SessionValidTTL)
762+
default:
763+
return ErrUserNotAuthorized
764+
}
765+
}
766+
767+
ok, err := p.provider.RefreshSession(session)
732768
// We failed to refresh the session successfully
733769
// clear the cookie and reject the request
734770
if err != nil {
@@ -757,9 +793,23 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) (er
757793
} else if session.ValidationPeriodExpired() {
758794
// Validation period has expired, this is the shortest interval we use to
759795
// check for valid requests. This should be set to something like a minute.
760-
// This calls up the provider chain to validate this user is still active
761-
// and hasn't been de-authorized.
762-
ok := p.provider.ValidateSessionState(session, allowedGroups)
796+
// In this case we:
797+
// - run any defined email domain, email address, and email group validators against the session
798+
// - call up the provider chain to validate this user is still active and hasn't been de-authorized.
799+
800+
err := p.runValidatorsWithGracePeriod(session)
801+
if err != nil {
802+
switch err {
803+
case providers.ErrAuthProviderUnavailable:
804+
tags = append(tags, "action:refresh_session", "error:validation_failed")
805+
p.StatsdClient.Incr("provider_error_fallback", tags, 1.0)
806+
session.RefreshDeadline = sessions.ExtendDeadline(p.provider.Data().SessionValidTTL)
807+
default:
808+
return ErrUserNotAuthorized
809+
}
810+
}
811+
812+
ok := p.provider.ValidateSessionToken(session)
763813
if !ok {
764814
// This user is now no longer authorized, or we failed to
765815
// validate the user.
@@ -781,25 +831,6 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) (er
781831
}
782832
}
783833

784-
// We revalidate group membership whenever the session is refreshed or revalidated
785-
// just above in the call to ValidateSessionState and RefreshSession.
786-
// To reduce strain on upstream identity providers we only revalidate email domains and
787-
// addresses on each request here.
788-
for _, v := range p.Validators {
789-
_, EmailGroupValidator := v.(options.EmailGroupValidator)
790-
791-
if !EmailGroupValidator {
792-
err := v.Validate(session)
793-
if err != nil {
794-
tags = append(tags, "error:validation_failed")
795-
p.StatsdClient.Incr("application_error", tags, 1.0)
796-
logger.WithRemoteAddress(remoteAddr).WithUser(session.Email).Info(
797-
fmt.Sprintf("permission denied: unauthorized: %q", err))
798-
return ErrUserNotAuthorized
799-
}
800-
}
801-
}
802-
803834
logger.WithRemoteAddress(remoteAddr).WithUser(session.Email).Info(
804835
fmt.Sprintf("authentication: user validated"))
805836

internal/proxy/providers/providers.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ type Provider interface {
1313
Redeem(string, string) (*sessions.SessionState, error)
1414
ValidateGroup(string, []string, string) ([]string, bool, error)
1515
UserGroups(string, []string, string) ([]string, error)
16-
ValidateSessionState(*sessions.SessionState, []string) bool
16+
ValidateSessionToken(*sessions.SessionState) bool
1717
GetSignInURL(redirectURL *url.URL, finalRedirect string) *url.URL
1818
GetSignOutURL(redirectURL *url.URL) *url.URL
19-
RefreshSession(*sessions.SessionState, []string) (bool, error)
19+
RefreshSession(*sessions.SessionState) (bool, error)
2020
}
2121

2222
// New returns a new sso Provider

internal/proxy/providers/singleflight_middleware.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ func (p *SingleFlightProvider) UserGroups(email string, groups []string, accessT
9494
return groups, nil
9595
}
9696

97-
// ValidateSessionState calls the provider's ValidateSessionState function and returns the response
98-
func (p *SingleFlightProvider) ValidateSessionState(s *sessions.SessionState, allowedGroups []string) bool {
99-
response, err := p.do("ValidateSessionState", s.AccessToken, func() (interface{}, error) {
100-
valid := p.provider.ValidateSessionState(s, allowedGroups)
97+
// ValidateSessionToken calls the provider's ValidateSessionToken function and returns the response
98+
func (p *SingleFlightProvider) ValidateSessionToken(s *sessions.SessionState) bool {
99+
response, err := p.do("ValidateSessionToken", s.AccessToken, func() (interface{}, error) {
100+
valid := p.provider.ValidateSessionToken(s)
101101
return valid, nil
102102
})
103103
if err != nil {
@@ -112,11 +112,11 @@ func (p *SingleFlightProvider) ValidateSessionState(s *sessions.SessionState, al
112112
return valid
113113
}
114114

115-
// RefreshSession takes in a SessionState and allowedGroups and
115+
// RefreshSession takes in a SessionState and
116116
// returns false if the session is not refreshed and true if it is.
117-
func (p *SingleFlightProvider) RefreshSession(s *sessions.SessionState, allowedGroups []string) (bool, error) {
117+
func (p *SingleFlightProvider) RefreshSession(s *sessions.SessionState) (bool, error) {
118118
response, err := p.do("RefreshSession", s.RefreshToken, func() (interface{}, error) {
119-
return p.provider.RefreshSession(s, allowedGroups)
119+
return p.provider.RefreshSession(s)
120120
})
121121
if err != nil {
122122
return false, err

internal/proxy/providers/sso.go

+17-69
Original file line numberDiff line numberDiff line change
@@ -98,17 +98,6 @@ func isProviderUnavailable(statusCode int) bool {
9898
return statusCode == http.StatusTooManyRequests || statusCode == http.StatusServiceUnavailable
9999
}
100100

101-
func extendDeadline(ttl time.Duration) time.Time {
102-
return time.Now().Add(ttl).Truncate(time.Second)
103-
}
104-
105-
func (p *SSOProvider) withinGracePeriod(s *sessions.SessionState) bool {
106-
if s.GracePeriodStart.IsZero() {
107-
s.GracePeriodStart = time.Now()
108-
}
109-
return s.GracePeriodStart.Add(p.GracePeriodTTL).After(time.Now())
110-
}
111-
112101
// Redeem takes a redirectURL and code and redeems the SessionState
113102
func (p *SSOProvider) Redeem(redirectURL, code string) (*sessions.SessionState, error) {
114103
if code == "" {
@@ -165,9 +154,9 @@ func (p *SSOProvider) Redeem(redirectURL, code string) (*sessions.SessionState,
165154
AccessToken: jsonResponse.AccessToken,
166155
RefreshToken: jsonResponse.RefreshToken,
167156

168-
RefreshDeadline: extendDeadline(time.Duration(jsonResponse.ExpiresIn) * time.Second),
169-
LifetimeDeadline: extendDeadline(p.SessionLifetimeTTL),
170-
ValidDeadline: extendDeadline(p.SessionValidTTL),
157+
RefreshDeadline: sessions.ExtendDeadline(time.Duration(jsonResponse.ExpiresIn) * time.Second),
158+
LifetimeDeadline: sessions.ExtendDeadline(p.SessionLifetimeTTL),
159+
ValidDeadline: sessions.ExtendDeadline(p.SessionValidTTL),
171160

172161
Email: jsonResponse.Email,
173162
User: user,
@@ -245,9 +234,9 @@ func (p *SSOProvider) UserGroups(email string, groups []string, accessToken stri
245234
return jsonResponse.Groups, nil
246235
}
247236

248-
// RefreshSession takes a SessionState and allowedGroups and refreshes the session access token,
237+
// RefreshSession takes a SessionState and refreshes the session access token,
249238
// returns `true` on success, and `false` on error
250-
func (p *SSOProvider) RefreshSession(s *sessions.SessionState, allowedGroups []string) (bool, error) {
239+
func (p *SSOProvider) RefreshSession(s *sessions.SessionState) (bool, error) {
251240
logger := log.NewLogEntry()
252241

253242
if s.RefreshToken == "" {
@@ -259,35 +248,17 @@ func (p *SSOProvider) RefreshSession(s *sessions.SessionState, allowedGroups []s
259248
// When we detect that the auth provider is not explicitly denying
260249
// authentication, and is merely unavailable, we refresh and continue
261250
// as normal during the "grace period"
262-
if err == ErrAuthProviderUnavailable && p.withinGracePeriod(s) {
251+
if err == ErrAuthProviderUnavailable && s.IsWithinGracePeriod(p.GracePeriodTTL) {
263252
tags := []string{"action:refresh_session", "error:redeem_token_failed"}
264253
p.StatsdClient.Incr("provider_error_fallback", tags, 1.0)
265-
s.RefreshDeadline = extendDeadline(p.SessionValidTTL)
254+
s.RefreshDeadline = sessions.ExtendDeadline(p.SessionValidTTL)
266255
return true, nil
267256
}
268257
return false, err
269258
}
270259

271-
inGroups, validGroup, err := p.ValidateGroup(s.Email, allowedGroups, newToken)
272-
if err != nil {
273-
// When we detect that the auth provider is not explicitly denying
274-
// authentication, and is merely unavailable, we refresh and continue
275-
// as normal during the "grace period"
276-
if err == ErrAuthProviderUnavailable && p.withinGracePeriod(s) {
277-
tags := []string{"action:refresh_session", "error:user_groups_failed"}
278-
p.StatsdClient.Incr("provider_error_fallback", tags, 1.0)
279-
s.RefreshDeadline = extendDeadline(p.SessionValidTTL)
280-
return true, nil
281-
}
282-
return false, err
283-
}
284-
if !validGroup {
285-
return false, errors.New("Group membership revoked")
286-
}
287-
s.Groups = inGroups
288-
289260
s.AccessToken = newToken
290-
s.RefreshDeadline = extendDeadline(duration)
261+
s.RefreshDeadline = sessions.ExtendDeadline(duration)
291262
s.GracePeriodStart = time.Time{}
292263
logger.WithUser(s.Email).WithRefreshDeadline(s.RefreshDeadline).Info("refreshed session access token")
293264
return true, nil
@@ -340,16 +311,16 @@ func (p *SSOProvider) redeemRefreshToken(refreshToken string) (token string, exp
340311
return
341312
}
342313

343-
// ValidateSessionState takes a sessionState and allowedGroups and validates the session state
344-
func (p *SSOProvider) ValidateSessionState(s *sessions.SessionState, allowedGroups []string) bool {
314+
// ValidateSessionToken takes a sessionState and validates the session token
315+
func (p *SSOProvider) ValidateSessionToken(s *sessions.SessionState) bool {
345316
logger := log.NewLogEntry()
346317

347318
// we validate the user's access token is valid
348319
params := url.Values{}
349320
params.Add("client_id", p.ClientID)
350321
req, err := p.newRequest("GET", fmt.Sprintf("%s?%s", p.ValidateURL.String(), params.Encode()), nil)
351322
if err != nil {
352-
logger.WithUser(s.Email).Error(err, "error validating session state")
323+
logger.WithUser(s.Email).Error(err, "error validating session access token")
353324
return false
354325
}
355326

@@ -358,52 +329,29 @@ func (p *SSOProvider) ValidateSessionState(s *sessions.SessionState, allowedGrou
358329

359330
resp, err := httpClient.Do(req)
360331
if err != nil {
361-
logger.WithUser(s.Email).Error("error making request to validate access token")
332+
logger.WithUser(s.Email).Error("error making request to validate session access token")
362333
return false
363334
}
364335

365336
if resp.StatusCode != 200 {
366337
// When we detect that the auth provider is not explicitly denying
367338
// authentication, and is merely unavailable, we validate and continue
368339
// as normal during the "grace period"
369-
if isProviderUnavailable(resp.StatusCode) && p.withinGracePeriod(s) {
340+
if isProviderUnavailable(resp.StatusCode) && s.IsWithinGracePeriod(p.GracePeriodTTL) {
370341
tags := []string{"action:validate_session", "error:validation_failed"}
371342
p.StatsdClient.Incr("provider_error_fallback", tags, 1.0)
372-
s.ValidDeadline = extendDeadline(p.SessionValidTTL)
343+
s.ValidDeadline = sessions.ExtendDeadline(p.SessionValidTTL)
373344
return true
374345
}
375346
logger.WithUser(s.Email).WithHTTPStatus(resp.StatusCode).Info(
376-
"could not validate user access token")
377-
return false
378-
}
379-
380-
// check the user is in the proper group(s)
381-
inGroups, validGroup, err := p.ValidateGroup(s.Email, allowedGroups, s.AccessToken)
382-
if err != nil {
383-
// When we detect that the auth provider is not explicitly denying
384-
// authentication, and is merely unavailable, we validate and continue
385-
// as normal during the "grace period"
386-
if err == ErrAuthProviderUnavailable && p.withinGracePeriod(s) {
387-
tags := []string{"action:validate_session", "error:user_groups_failed"}
388-
p.StatsdClient.Incr("provider_error_fallback", tags, 1.0)
389-
s.ValidDeadline = extendDeadline(p.SessionValidTTL)
390-
return true
391-
}
392-
logger.WithUser(s.Email).Error(err, "error fetching group memberships")
393-
return false
394-
}
395-
396-
if !validGroup {
397-
logger.WithUser(s.Email).WithAllowedGroups(allowedGroups).Info(
398-
"user is no longer in valid groups")
347+
"could not validate session access token")
399348
return false
400349
}
401-
s.Groups = inGroups
402350

403-
s.ValidDeadline = extendDeadline(p.SessionValidTTL)
351+
s.ValidDeadline = sessions.ExtendDeadline(p.SessionValidTTL)
404352
s.GracePeriodStart = time.Time{}
405353

406-
logger.WithUser(s.Email).WithSessionValid(s.ValidDeadline).Info("validated session")
354+
logger.WithUser(s.Email).WithSessionValid(s.ValidDeadline).Info("validated session access token")
407355

408356
return true
409357
}

0 commit comments

Comments
 (0)