fix(handlers): verify handler (#3956)

When an anonymous user tries to access a forbidden resource with no subject, we should response with 403.

Fixes #3084
pull/3955/head^2
Manuel Nuñez 2022-09-04 19:21:30 -03:00 committed by GitHub
parent 6cc182de08
commit ca85992ac6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 13 additions and 13 deletions

View File

@ -54,7 +54,7 @@ func (p Authorizer) IsSecondFactorEnabled() bool {
} }
// GetRequiredLevel retrieve the required level of authorization to access the object. // GetRequiredLevel retrieve the required level of authorization to access the object.
func (p Authorizer) GetRequiredLevel(subject Subject, object Object) Level { func (p Authorizer) GetRequiredLevel(subject Subject, object Object) (bool, Level) {
logger := logging.Logger() logger := logging.Logger()
logger.Debugf("Check authorization of subject %s and object %s (method %s).", logger.Debugf("Check authorization of subject %s and object %s (method %s).",
@ -64,7 +64,7 @@ func (p Authorizer) GetRequiredLevel(subject Subject, object Object) Level {
if rule.IsMatch(subject, object) { if rule.IsMatch(subject, object) {
logger.Tracef(traceFmtACLHitMiss, "HIT", rule.Position, subject.String(), object.String(), object.Method) logger.Tracef(traceFmtACLHitMiss, "HIT", rule.Position, subject.String(), object.String(), object.Method)
return rule.Policy return len(rule.Subjects) > 0, rule.Policy
} }
logger.Tracef(traceFmtACLHitMiss, "MISS", rule.Position, subject.String(), object.String(), object.Method) logger.Tracef(traceFmtACLHitMiss, "MISS", rule.Position, subject.String(), object.String(), object.Method)
@ -73,7 +73,7 @@ func (p Authorizer) GetRequiredLevel(subject Subject, object Object) Level {
logger.Debugf("No matching rule for subject %s and url %s... Applying default policy.", logger.Debugf("No matching rule for subject %s and url %s... Applying default policy.",
subject.String(), object.String()) subject.String(), object.String())
return p.defaultPolicy return false, p.defaultPolicy
} }
// GetRuleMatchResults iterates through the rules and produces a list of RuleMatchResult provided a subject and object. // GetRuleMatchResults iterates through the rules and produces a list of RuleMatchResult provided a subject and object.

View File

@ -36,7 +36,7 @@ func (s *AuthorizerTester) CheckAuthorizations(t *testing.T, subject Subject, re
object := NewObject(targetURL, method) object := NewObject(targetURL, method)
level := s.GetRequiredLevel(subject, object) _, level := s.GetRequiredLevel(subject, object)
assert.Equal(t, expectedLevel, level) assert.Equal(t, expectedLevel, level)
} }

View File

@ -56,7 +56,7 @@ func parseBasicAuth(header []byte, auth string) (username, password string, err
// isTargetURLAuthorized check whether the given user is authorized to access the resource. // isTargetURLAuthorized check whether the given user is authorized to access the resource.
func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.URL, func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.URL,
username string, userGroups []string, clientIP net.IP, method []byte, authLevel authentication.Level) authorizationMatching { username string, userGroups []string, clientIP net.IP, method []byte, authLevel authentication.Level) authorizationMatching {
level := authorizer.GetRequiredLevel( hasSubject, level := authorizer.GetRequiredLevel(
authorization.Subject{ authorization.Subject{
Username: username, Username: username,
Groups: userGroups, Groups: userGroups,
@ -67,13 +67,12 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U
switch { switch {
case level == authorization.Bypass: case level == authorization.Bypass:
return Authorized return Authorized
case level == authorization.Denied && username != "": case level == authorization.Denied && (username != "" || !hasSubject):
// If the user is not anonymous, it means that we went through // If the user is not anonymous, it means that we went through
// all the rules related to that user and knowing who he is we can // all the rules related to that user and knowing who he is we can
// deduce the access is forbidden // deduce the access is forbidden
// For anonymous users though, we cannot be sure that she // For anonymous users though, we check that the matched rule has no subject
// could not be granted the rights to access the resource. Consequently // if matched rule has not subject then this rule applies to all users including anonymous.
// for anonymous users we send Unauthorized instead of Forbidden.
return Forbidden return Forbidden
case level == authorization.OneFactor && authLevel >= authentication.OneFactor, case level == authorization.OneFactor && authLevel >= authentication.OneFactor,
level == authorization.TwoFactor && authLevel >= authentication.TwoFactor: level == authorization.TwoFactor && authLevel >= authentication.TwoFactor:

View File

@ -140,7 +140,7 @@ func TestShouldCheckAuthorizationMatching(t *testing.T) {
{"two_factor", authentication.OneFactor, NotAuthorized}, {"two_factor", authentication.OneFactor, NotAuthorized},
{"two_factor", authentication.TwoFactor, Authorized}, {"two_factor", authentication.TwoFactor, Authorized},
{"deny", authentication.NotAuthenticated, NotAuthorized}, {"deny", authentication.NotAuthenticated, Forbidden},
{"deny", authentication.OneFactor, Forbidden}, {"deny", authentication.OneFactor, Forbidden},
{"deny", authentication.TwoFactor, Forbidden}, {"deny", authentication.TwoFactor, Forbidden},
} }
@ -508,11 +508,12 @@ func (p Pair) String() string {
func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) { func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) {
testCases := []Pair{ testCases := []Pair{
{"https://test.example.com", "", nil, authentication.NotAuthenticated, 401}, // should apply default policy.
{"https://test.example.com", "", nil, authentication.NotAuthenticated, 403},
{"https://bypass.example.com", "", nil, authentication.NotAuthenticated, 200}, {"https://bypass.example.com", "", nil, authentication.NotAuthenticated, 200},
{"https://one-factor.example.com", "", nil, authentication.NotAuthenticated, 401}, {"https://one-factor.example.com", "", nil, authentication.NotAuthenticated, 401},
{"https://two-factor.example.com", "", nil, authentication.NotAuthenticated, 401}, {"https://two-factor.example.com", "", nil, authentication.NotAuthenticated, 401},
{"https://deny.example.com", "", nil, authentication.NotAuthenticated, 401}, {"https://deny.example.com", "", nil, authentication.NotAuthenticated, 403},
{"https://test.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 403}, {"https://test.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 403},
{"https://bypass.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 200}, {"https://bypass.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 200},

View File

@ -87,7 +87,7 @@ func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI, requestMethod st
return return
} }
requiredLevel := ctx.Providers.Authorizer.GetRequiredLevel( _, requiredLevel := ctx.Providers.Authorizer.GetRequiredLevel(
authorization.Subject{ authorization.Subject{
Username: username, Username: username,
Groups: groups, Groups: groups,