fix(authorization): configuration reports 2fa disabled with 2fa oidc clients (#2089)

This resolves an issue where if you have zero two_factor ACL rules but enabled two_factor OIDC clients, 2FA is reported as disabled.
This commit is contained in:
James Elliott 2021-06-18 11:38:01 +10:00 committed by GitHub
parent 438555886e
commit ef3c2faeb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 274 additions and 162 deletions

View File

@ -121,7 +121,7 @@ func startServer() {
} }
clock := utils.RealClock{} clock := utils.RealClock{}
authorizer := authorization.NewAuthorizer(config.AccessControl) authorizer := authorization.NewAuthorizer(config)
sessionProvider := session.NewProvider(config.Session, autheliaCertPool) sessionProvider := session.NewProvider(config.Session, autheliaCertPool)
regulator := regulation.NewRegulator(config.Regulation, storageProvider, clock) regulator := regulation.NewRegulator(config.Regulation, storageProvider, clock)
oidcProvider, err := oidc.NewOpenIDConnectProvider(config.IdentityProviders.OIDC) oidcProvider, err := oidc.NewOpenIDConnectProvider(config.IdentityProviders.OIDC)

View File

@ -1,8 +1,6 @@
package authorization package authorization
import ( import (
"github.com/sirupsen/logrus"
"github.com/authelia/authelia/internal/configuration/schema" "github.com/authelia/authelia/internal/configuration/schema"
"github.com/authelia/authelia/internal/logging" "github.com/authelia/authelia/internal/logging"
) )
@ -11,25 +9,20 @@ import (
type Authorizer struct { type Authorizer struct {
defaultPolicy Level defaultPolicy Level
rules []*AccessControlRule rules []*AccessControlRule
configuration *schema.Configuration
} }
// NewAuthorizer create an instance of authorizer with a given access control configuration. // NewAuthorizer create an instance of authorizer with a given access control configuration.
func NewAuthorizer(configuration schema.AccessControlConfiguration) *Authorizer { func NewAuthorizer(configuration *schema.Configuration) *Authorizer {
if logging.Logger().IsLevelEnabled(logrus.TraceLevel) {
return &Authorizer{
defaultPolicy: PolicyToLevel(configuration.DefaultPolicy),
rules: NewAccessControlRules(configuration),
}
}
return &Authorizer{ return &Authorizer{
defaultPolicy: PolicyToLevel(configuration.DefaultPolicy), defaultPolicy: PolicyToLevel(configuration.AccessControl.DefaultPolicy),
rules: NewAccessControlRules(configuration), rules: NewAccessControlRules(configuration.AccessControl),
configuration: configuration,
} }
} }
// IsSecondFactorEnabled return true if at least one policy is set to second factor. // IsSecondFactorEnabled return true if at least one policy is set to second factor.
func (p *Authorizer) IsSecondFactorEnabled() bool { func (p Authorizer) IsSecondFactorEnabled() bool {
if p.defaultPolicy == TwoFactor { if p.defaultPolicy == TwoFactor {
return true return true
} }
@ -40,6 +33,14 @@ func (p *Authorizer) IsSecondFactorEnabled() bool {
} }
} }
if p.configuration.IdentityProviders.OIDC != nil {
for _, client := range p.configuration.IdentityProviders.OIDC.Clients {
if client.Policy == twoFactor {
return true
}
}
}
return false return false
} }

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/authelia/authelia/internal/configuration/schema" "github.com/authelia/authelia/internal/configuration/schema"
@ -20,8 +21,12 @@ type AuthorizerTester struct {
} }
func NewAuthorizerTester(config schema.AccessControlConfiguration) *AuthorizerTester { func NewAuthorizerTester(config schema.AccessControlConfiguration) *AuthorizerTester {
fullConfig := &schema.Configuration{
AccessControl: config,
}
return &AuthorizerTester{ return &AuthorizerTester{
NewAuthorizer(config), NewAuthorizer(fullConfig),
} }
} }
@ -102,7 +107,7 @@ var Sally = UserWithIPv6AddressAndGroups
func (s *AuthorizerSuite) TestShouldCheckDefaultBypassConfig() { func (s *AuthorizerSuite) TestShouldCheckDefaultBypassConfig() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("bypass").Build() WithDefaultPolicy(bypass).Build()
tester.CheckAuthorizations(s.T(), AnonymousUser, "https://public.example.com/", "GET", Bypass) tester.CheckAuthorizations(s.T(), AnonymousUser, "https://public.example.com/", "GET", Bypass)
tester.CheckAuthorizations(s.T(), UserWithGroups, "https://public.example.com/", "GET", Bypass) tester.CheckAuthorizations(s.T(), UserWithGroups, "https://public.example.com/", "GET", Bypass)
@ -112,7 +117,7 @@ func (s *AuthorizerSuite) TestShouldCheckDefaultBypassConfig() {
func (s *AuthorizerSuite) TestShouldCheckDefaultDeniedConfig() { func (s *AuthorizerSuite) TestShouldCheckDefaultDeniedConfig() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny").Build() WithDefaultPolicy(deny).Build()
tester.CheckAuthorizations(s.T(), AnonymousUser, "https://public.example.com/", "GET", Denied) tester.CheckAuthorizations(s.T(), AnonymousUser, "https://public.example.com/", "GET", Denied)
tester.CheckAuthorizations(s.T(), UserWithGroups, "https://public.example.com/", "GET", Denied) tester.CheckAuthorizations(s.T(), UserWithGroups, "https://public.example.com/", "GET", Denied)
@ -122,10 +127,10 @@ func (s *AuthorizerSuite) TestShouldCheckDefaultDeniedConfig() {
func (s *AuthorizerSuite) TestShouldCheckMultiDomainRule() { func (s *AuthorizerSuite) TestShouldCheckMultiDomainRule() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"*.example.com"}, Domains: []string{"*.example.com"},
Policy: "bypass", Policy: bypass,
}). }).
Build() Build()
@ -139,14 +144,14 @@ func (s *AuthorizerSuite) TestShouldCheckMultiDomainRule() {
func (s *AuthorizerSuite) TestShouldCheckDynamicDomainRules() { func (s *AuthorizerSuite) TestShouldCheckDynamicDomainRules() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"{user}.example.com"}, Domains: []string{"{user}.example.com"},
Policy: "bypass", Policy: bypass,
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"{group}.example.com"}, Domains: []string{"{group}.example.com"},
Policy: "bypass", Policy: bypass,
}). }).
Build() Build()
@ -158,10 +163,10 @@ func (s *AuthorizerSuite) TestShouldCheckDynamicDomainRules() {
func (s *AuthorizerSuite) TestShouldCheckMultipleDomainRule() { func (s *AuthorizerSuite) TestShouldCheckMultipleDomainRule() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"*.example.com", "other.com"}, Domains: []string{"*.example.com", "other.com"},
Policy: "bypass", Policy: bypass,
}). }).
Build() Build()
@ -178,18 +183,18 @@ func (s *AuthorizerSuite) TestShouldCheckMultipleDomainRule() {
func (s *AuthorizerSuite) TestShouldCheckFactorsPolicy() { func (s *AuthorizerSuite) TestShouldCheckFactorsPolicy() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"single.example.com"}, Domains: []string{"single.example.com"},
Policy: "one_factor", Policy: oneFactor,
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"protected.example.com"}, Domains: []string{"protected.example.com"},
Policy: "two_factor", Policy: twoFactor,
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"public.example.com"}, Domains: []string{"public.example.com"},
Policy: "bypass", Policy: bypass,
}). }).
Build() Build()
@ -201,19 +206,19 @@ func (s *AuthorizerSuite) TestShouldCheckFactorsPolicy() {
func (s *AuthorizerSuite) TestShouldCheckRulePrecedence() { func (s *AuthorizerSuite) TestShouldCheckRulePrecedence() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"protected.example.com"}, Domains: []string{"protected.example.com"},
Policy: "bypass", Policy: bypass,
Subjects: [][]string{{"user:john"}}, Subjects: [][]string{{"user:john"}},
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"protected.example.com"}, Domains: []string{"protected.example.com"},
Policy: "one_factor", Policy: oneFactor,
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"*.example.com"}, Domains: []string{"*.example.com"},
Policy: "two_factor", Policy: twoFactor,
}). }).
Build() Build()
@ -224,10 +229,10 @@ func (s *AuthorizerSuite) TestShouldCheckRulePrecedence() {
func (s *AuthorizerSuite) TestShouldCheckUserMatching() { func (s *AuthorizerSuite) TestShouldCheckUserMatching() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"protected.example.com"}, Domains: []string{"protected.example.com"},
Policy: "one_factor", Policy: oneFactor,
Subjects: [][]string{{"user:john"}}, Subjects: [][]string{{"user:john"}},
}). }).
Build() Build()
@ -238,10 +243,10 @@ func (s *AuthorizerSuite) TestShouldCheckUserMatching() {
func (s *AuthorizerSuite) TestShouldCheckGroupMatching() { func (s *AuthorizerSuite) TestShouldCheckGroupMatching() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"protected.example.com"}, Domains: []string{"protected.example.com"},
Policy: "one_factor", Policy: oneFactor,
Subjects: [][]string{{"group:admins"}}, Subjects: [][]string{{"group:admins"}},
}). }).
Build() Build()
@ -252,10 +257,10 @@ func (s *AuthorizerSuite) TestShouldCheckGroupMatching() {
func (s *AuthorizerSuite) TestShouldCheckSubjectsMatching() { func (s *AuthorizerSuite) TestShouldCheckSubjectsMatching() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"protected.example.com"}, Domains: []string{"protected.example.com"},
Policy: "one_factor", Policy: oneFactor,
Subjects: [][]string{{"group:admins"}, {"user:bob"}}, Subjects: [][]string{{"group:admins"}, {"user:bob"}},
}). }).
Build() Build()
@ -268,10 +273,10 @@ func (s *AuthorizerSuite) TestShouldCheckSubjectsMatching() {
func (s *AuthorizerSuite) TestShouldCheckMultipleSubjectsMatching() { func (s *AuthorizerSuite) TestShouldCheckMultipleSubjectsMatching() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"protected.example.com"}, Domains: []string{"protected.example.com"},
Policy: "one_factor", Policy: oneFactor,
Subjects: [][]string{{"group:admins", "user:bob"}, {"group:admins", "group:dev"}}, Subjects: [][]string{{"group:admins", "user:bob"}, {"group:admins", "group:dev"}},
}). }).
Build() Build()
@ -283,30 +288,30 @@ func (s *AuthorizerSuite) TestShouldCheckMultipleSubjectsMatching() {
func (s *AuthorizerSuite) TestShouldCheckIPMatching() { func (s *AuthorizerSuite) TestShouldCheckIPMatching() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"protected.example.com"}, Domains: []string{"protected.example.com"},
Policy: "bypass", Policy: bypass,
Networks: []string{"192.168.1.8", "10.0.0.8"}, Networks: []string{"192.168.1.8", "10.0.0.8"},
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"protected.example.com"}, Domains: []string{"protected.example.com"},
Policy: "one_factor", Policy: oneFactor,
Networks: []string{"10.0.0.7"}, Networks: []string{"10.0.0.7"},
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"net.example.com"}, Domains: []string{"net.example.com"},
Policy: "two_factor", Policy: twoFactor,
Networks: []string{"10.0.0.0/8"}, Networks: []string{"10.0.0.0/8"},
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"ipv6.example.com"}, Domains: []string{"ipv6.example.com"},
Policy: "two_factor", Policy: twoFactor,
Networks: []string{"fec0::1/64"}, Networks: []string{"fec0::1/64"},
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"ipv6-alt.example.com"}, Domains: []string{"ipv6-alt.example.com"},
Policy: "two_factor", Policy: twoFactor,
Networks: []string{"fec0::1"}, Networks: []string{"fec0::1"},
}). }).
Build() Build()
@ -327,20 +332,20 @@ func (s *AuthorizerSuite) TestShouldCheckIPMatching() {
func (s *AuthorizerSuite) TestShouldCheckMethodMatching() { func (s *AuthorizerSuite) TestShouldCheckMethodMatching() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"protected.example.com"}, Domains: []string{"protected.example.com"},
Policy: "bypass", Policy: bypass,
Methods: []string{"OPTIONS", "HEAD", "GET", "CONNECT", "TRACE"}, Methods: []string{"OPTIONS", "HEAD", "GET", "CONNECT", "TRACE"},
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"protected.example.com"}, Domains: []string{"protected.example.com"},
Policy: "one_factor", Policy: oneFactor,
Methods: []string{"PUT", "PATCH", "POST"}, Methods: []string{"PUT", "PATCH", "POST"},
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"protected.example.com"}, Domains: []string{"protected.example.com"},
Policy: "two_factor", Policy: twoFactor,
Methods: []string{"DELETE"}, Methods: []string{"DELETE"},
}). }).
Build() Build()
@ -360,15 +365,15 @@ func (s *AuthorizerSuite) TestShouldCheckMethodMatching() {
func (s *AuthorizerSuite) TestShouldCheckResourceMatching() { func (s *AuthorizerSuite) TestShouldCheckResourceMatching() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"resource.example.com"}, Domains: []string{"resource.example.com"},
Policy: "bypass", Policy: bypass,
Resources: []string{"^/bypass/[a-z]+$", "^/$", "embedded"}, Resources: []string{"^/bypass/[a-z]+$", "^/$", "embedded"},
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"resource.example.com"}, Domains: []string{"resource.example.com"},
Policy: "one_factor", Policy: oneFactor,
Resources: []string{"^/one_factor/[a-z]+$"}, Resources: []string{"^/one_factor/[a-z]+$"},
}). }).
Build() Build()
@ -385,15 +390,15 @@ func (s *AuthorizerSuite) TestShouldCheckResourceMatching() {
func (s *AuthorizerSuite) TestShouldMatchAnyDomainIfBlank() { func (s *AuthorizerSuite) TestShouldMatchAnyDomainIfBlank() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Policy: "bypass", Policy: bypass,
Methods: []string{"OPTIONS", "HEAD", "GET", "CONNECT", "TRACE"}, Methods: []string{"OPTIONS", "HEAD", "GET", "CONNECT", "TRACE"},
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Policy: "one_factor", Policy: oneFactor,
Methods: []string{"PUT", "PATCH"}, Methods: []string{"PUT", "PATCH"},
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Policy: "two_factor", Policy: twoFactor,
Methods: []string{"DELETE"}, Methods: []string{"DELETE"},
}). }).
Build() Build()
@ -417,41 +422,41 @@ func (s *AuthorizerSuite) TestShouldMatchAnyDomainIfBlank() {
func (s *AuthorizerSuite) TestShouldMatchResourceWithSubjectRules() { func (s *AuthorizerSuite) TestShouldMatchResourceWithSubjectRules() {
tester := NewAuthorizerBuilder(). tester := NewAuthorizerBuilder().
WithDefaultPolicy("deny"). WithDefaultPolicy(deny).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"public.example.com"}, Domains: []string{"public.example.com"},
Resources: []string{"^/admin/.*$"}, Resources: []string{"^/admin/.*$"},
Subjects: [][]string{{"group:admins"}}, Subjects: [][]string{{"group:admins"}},
Policy: "one_factor", Policy: oneFactor,
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"public.example.com"}, Domains: []string{"public.example.com"},
Resources: []string{"^/admin/.*$"}, Resources: []string{"^/admin/.*$"},
Policy: "deny", Policy: deny,
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"public.example.com"}, Domains: []string{"public.example.com"},
Policy: "bypass", Policy: bypass,
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"public2.example.com"}, Domains: []string{"public2.example.com"},
Resources: []string{"^/admin/.*$"}, Resources: []string{"^/admin/.*$"},
Subjects: [][]string{{"group:admins"}}, Subjects: [][]string{{"group:admins"}},
Policy: "bypass", Policy: bypass,
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"public2.example.com"}, Domains: []string{"public2.example.com"},
Resources: []string{"^/admin/.*$"}, Resources: []string{"^/admin/.*$"},
Policy: "deny", Policy: deny,
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"public2.example.com"}, Domains: []string{"public2.example.com"},
Policy: "bypass", Policy: bypass,
}). }).
WithRule(schema.ACLRule{ WithRule(schema.ACLRule{
Domains: []string{"private.example.com"}, Domains: []string{"private.example.com"},
Subjects: [][]string{{"group:admins"}}, Subjects: [][]string{{"group:admins"}},
Policy: "two_factor", Policy: twoFactor,
}). }).
Build() Build()
@ -479,10 +484,10 @@ func (s *AuthorizerSuite) TestShouldMatchResourceWithSubjectRules() {
} }
func (s *AuthorizerSuite) TestPolicyToLevel() { func (s *AuthorizerSuite) TestPolicyToLevel() {
s.Assert().Equal(Bypass, PolicyToLevel("bypass")) s.Assert().Equal(Bypass, PolicyToLevel(bypass))
s.Assert().Equal(OneFactor, PolicyToLevel("one_factor")) s.Assert().Equal(OneFactor, PolicyToLevel(oneFactor))
s.Assert().Equal(TwoFactor, PolicyToLevel("two_factor")) s.Assert().Equal(TwoFactor, PolicyToLevel(twoFactor))
s.Assert().Equal(Denied, PolicyToLevel("deny")) s.Assert().Equal(Denied, PolicyToLevel(deny))
s.Assert().Equal(Denied, PolicyToLevel("whatever")) s.Assert().Equal(Denied, PolicyToLevel("whatever"))
} }
@ -491,3 +496,103 @@ func TestRunSuite(t *testing.T) {
s := AuthorizerSuite{} s := AuthorizerSuite{}
suite.Run(t, &s) suite.Run(t, &s)
} }
func TestNewAuthorizer(t *testing.T) {
config := &schema.Configuration{
AccessControl: schema.AccessControlConfiguration{
DefaultPolicy: deny,
Rules: []schema.ACLRule{
{
Domains: []string{"example.com"},
Policy: twoFactor,
Subjects: [][]string{
{
"user:admin",
},
{
"group:admins",
},
},
},
},
},
}
authorizer := NewAuthorizer(config)
assert.Equal(t, Denied, authorizer.defaultPolicy)
assert.Equal(t, TwoFactor, authorizer.rules[0].Policy)
user, ok := authorizer.rules[0].Subjects[0].Subjects[0].(AccessControlUser)
require.True(t, ok)
assert.Equal(t, "admin", user.Name)
group, ok := authorizer.rules[0].Subjects[1].Subjects[0].(AccessControlGroup)
require.True(t, ok)
assert.Equal(t, "admins", group.Name)
}
func TestAuthorizerIsSecondFactorEnabledRuleWithNoOIDC(t *testing.T) {
config := &schema.Configuration{
AccessControl: schema.AccessControlConfiguration{
DefaultPolicy: deny,
Rules: []schema.ACLRule{
{
Domains: []string{"example.com"},
Policy: oneFactor,
},
},
},
}
authorizer := NewAuthorizer(config)
assert.False(t, authorizer.IsSecondFactorEnabled())
authorizer.rules[0].Policy = TwoFactor
assert.True(t, authorizer.IsSecondFactorEnabled())
}
func TestAuthorizerIsSecondFactorEnabledRuleWithOIDC(t *testing.T) {
config := &schema.Configuration{
AccessControl: schema.AccessControlConfiguration{
DefaultPolicy: deny,
Rules: []schema.ACLRule{
{
Domains: []string{"example.com"},
Policy: oneFactor,
},
},
},
IdentityProviders: schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
Clients: []schema.OpenIDConnectClientConfiguration{
{
Policy: oneFactor,
},
},
},
},
}
authorizer := NewAuthorizer(config)
assert.False(t, authorizer.IsSecondFactorEnabled())
authorizer.rules[0].Policy = TwoFactor
assert.True(t, authorizer.IsSecondFactorEnabled())
authorizer.rules[0].Policy = OneFactor
assert.False(t, authorizer.IsSecondFactorEnabled())
config.IdentityProviders.OIDC.Clients[0].Policy = twoFactor
assert.True(t, authorizer.IsSecondFactorEnabled())
authorizer.rules[0].Policy = OneFactor
config.IdentityProviders.OIDC.Clients[0].Policy = oneFactor
assert.False(t, authorizer.IsSecondFactorEnabled())
authorizer.defaultPolicy = TwoFactor
assert.True(t, authorizer.IsSecondFactorEnabled())
}

View File

@ -17,4 +17,9 @@ const (
const userPrefix = "user:" const userPrefix = "user:"
const groupPrefix = "group:" const groupPrefix = "group:"
const bypass = "bypass"
const oneFactor = "one_factor"
const twoFactor = "two_factor"
const deny = "deny"
const traceFmtACLHitMiss = "ACL %s Position %d for subject %s and object %s (Method %s)" const traceFmtACLHitMiss = "ACL %s Position %d for subject %s and object %s (Method %s)"

View File

@ -12,13 +12,13 @@ import (
// PolicyToLevel converts a string policy to int authorization level. // PolicyToLevel converts a string policy to int authorization level.
func PolicyToLevel(policy string) Level { func PolicyToLevel(policy string) Level {
switch policy { switch policy {
case "bypass": case bypass:
return Bypass return Bypass
case "one_factor": case oneFactor:
return OneFactor return OneFactor
case "two_factor": case twoFactor:
return TwoFactor return TwoFactor
case "deny": case deny:
return Denied return Denied
} }
// By default the deny policy applies. // By default the deny policy applies.

View File

@ -23,6 +23,7 @@ func ConfigurationGet(ctx *middlewares.AutheliaCtx) {
} }
body.SecondFactorEnabled = ctx.Providers.Authorizer.IsSecondFactorEnabled() body.SecondFactorEnabled = ctx.Providers.Authorizer.IsSecondFactorEnabled()
ctx.Logger.Tracef("Second factor enabled: %v", body.SecondFactorEnabled) ctx.Logger.Tracef("Second factor enabled: %v", body.SecondFactorEnabled)
ctx.Logger.Tracef("Available methods are %s", body.AvailableMethods) ctx.Logger.Tracef("Available methods are %s", body.AvailableMethods)

View File

@ -17,10 +17,11 @@ type SecondFactorAvailableMethodsFixture struct {
func (s *SecondFactorAvailableMethodsFixture) SetupTest() { func (s *SecondFactorAvailableMethodsFixture) SetupTest() {
s.mock = mocks.NewMockAutheliaCtx(s.T()) s.mock = mocks.NewMockAutheliaCtx(s.T())
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&schema.Configuration{
DefaultPolicy: "deny", AccessControl: schema.AccessControlConfiguration{
Rules: []schema.ACLRule{}, DefaultPolicy: "deny",
}) Rules: []schema.ACLRule{},
}})
} }
func (s *SecondFactorAvailableMethodsFixture) TearDownTest() { func (s *SecondFactorAvailableMethodsFixture) TearDownTest() {
@ -66,23 +67,25 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsDisab
Period: schema.DefaultTOTPConfiguration.Period, Period: schema.DefaultTOTPConfiguration.Period,
}, },
} }
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(
DefaultPolicy: "bypass", &schema.Configuration{
Rules: []schema.ACLRule{ AccessControl: schema.AccessControlConfiguration{
{ DefaultPolicy: "bypass",
Domains: []string{"example.com"}, Rules: []schema.ACLRule{
Policy: "deny", {
}, Domains: []string{"example.com"},
{ Policy: "deny",
Domains: []string{"abc.example.com"}, },
Policy: "single_factor", {
}, Domains: []string{"abc.example.com"},
{ Policy: "single_factor",
Domains: []string{"def.example.com"}, },
Policy: "bypass", {
}, Domains: []string{"def.example.com"},
}, Policy: "bypass",
}) },
},
}})
ConfigurationGet(s.mock.Ctx) ConfigurationGet(s.mock.Ctx)
s.mock.Assert200OK(s.T(), ConfigurationBody{ s.mock.Assert200OK(s.T(), ConfigurationBody{
AvailableMethods: []string{"totp", "u2f"}, AvailableMethods: []string{"totp", "u2f"},
@ -97,23 +100,24 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabl
Period: schema.DefaultTOTPConfiguration.Period, Period: schema.DefaultTOTPConfiguration.Period,
}, },
} }
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&schema.Configuration{
DefaultPolicy: "two_factor", AccessControl: schema.AccessControlConfiguration{
Rules: []schema.ACLRule{ DefaultPolicy: "two_factor",
{ Rules: []schema.ACLRule{
Domains: []string{"example.com"}, {
Policy: "deny", Domains: []string{"example.com"},
Policy: "deny",
},
{
Domains: []string{"abc.example.com"},
Policy: "single_factor",
},
{
Domains: []string{"def.example.com"},
Policy: "bypass",
},
}, },
{ }})
Domains: []string{"abc.example.com"},
Policy: "single_factor",
},
{
Domains: []string{"def.example.com"},
Policy: "bypass",
},
},
})
ConfigurationGet(s.mock.Ctx) ConfigurationGet(s.mock.Ctx)
s.mock.Assert200OK(s.T(), ConfigurationBody{ s.mock.Assert200OK(s.T(), ConfigurationBody{
AvailableMethods: []string{"totp", "u2f"}, AvailableMethods: []string{"totp", "u2f"},
@ -128,23 +132,25 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldCheckSecondFactorIsEnabl
Period: schema.DefaultTOTPConfiguration.Period, Period: schema.DefaultTOTPConfiguration.Period,
}, },
} }
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(
DefaultPolicy: "bypass", &schema.Configuration{
Rules: []schema.ACLRule{ AccessControl: schema.AccessControlConfiguration{
{ DefaultPolicy: "bypass",
Domains: []string{"example.com"}, Rules: []schema.ACLRule{
Policy: "deny", {
}, Domains: []string{"example.com"},
{ Policy: "deny",
Domains: []string{"abc.example.com"}, },
Policy: "two_factor", {
}, Domains: []string{"abc.example.com"},
{ Policy: "two_factor",
Domains: []string{"def.example.com"}, },
Policy: "bypass", {
}, Domains: []string{"def.example.com"},
}, Policy: "bypass",
}) },
},
}})
ConfigurationGet(s.mock.Ctx) ConfigurationGet(s.mock.Ctx)
s.mock.Assert200OK(s.T(), ConfigurationBody{ s.mock.Assert200OK(s.T(), ConfigurationBody{
AvailableMethods: []string{"totp", "u2f"}, AvailableMethods: []string{"totp", "u2f"},

View File

@ -288,8 +288,7 @@ func (s *FirstFactorRedirectionSuite) SetupTest() {
Policy: "one_factor", Policy: "one_factor",
}, },
} }
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer( s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&s.mock.Ctx.Configuration)
s.mock.Ctx.Configuration.AccessControl)
s.mock.UserProviderMock. s.mock.UserProviderMock.
EXPECT(). EXPECT().
@ -360,8 +359,10 @@ func (s *FirstFactorRedirectionSuite) TestShouldRedirectToDefaultURLWhenURLIsUns
// Then: // Then:
// the user should receive 200 without redirection URL. // the user should receive 200 without redirection URL.
func (s *FirstFactorRedirectionSuite) TestShouldReply200WhenNoTargetURLProvidedAndTwoFactorEnabled() { func (s *FirstFactorRedirectionSuite) TestShouldReply200WhenNoTargetURLProvidedAndTwoFactorEnabled() {
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&schema.Configuration{
DefaultPolicy: "two_factor", AccessControl: schema.AccessControlConfiguration{
DefaultPolicy: "two_factor",
},
}) })
s.mock.Ctx.Request.SetBodyString(`{ s.mock.Ctx.Request.SetBodyString(`{
"username": "test", "username": "test",
@ -381,19 +382,20 @@ func (s *FirstFactorRedirectionSuite) TestShouldReply200WhenNoTargetURLProvidedA
// Then: // Then:
// the user should receive 200 without redirection URL. // the user should receive 200 without redirection URL.
func (s *FirstFactorRedirectionSuite) TestShouldReply200WhenUnsafeTargetURLProvidedAndTwoFactorEnabled() { func (s *FirstFactorRedirectionSuite) TestShouldReply200WhenUnsafeTargetURLProvidedAndTwoFactorEnabled() {
s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(schema.AccessControlConfiguration{ s.mock.Ctx.Providers.Authorizer = authorization.NewAuthorizer(&schema.Configuration{
DefaultPolicy: "one_factor", AccessControl: schema.AccessControlConfiguration{
Rules: []schema.ACLRule{ DefaultPolicy: "one_factor",
{ Rules: []schema.ACLRule{
Domains: []string{"test.example.com"}, {
Policy: "one_factor", Domains: []string{"test.example.com"},
Policy: "one_factor",
},
{
Domains: []string{"example.com"},
Policy: "two_factor",
},
}, },
{ }})
Domains: []string{"example.com"},
Policy: "two_factor",
},
},
})
s.mock.Ctx.Request.SetBodyString(`{ s.mock.Ctx.Request.SetBodyString(`{
"username": "test", "username": "test",
"password": "hello", "password": "hello",

View File

@ -147,13 +147,14 @@ func TestShouldCheckAuthorizationMatching(t *testing.T) {
url, _ := url.ParseRequestURI("https://test.example.com") url, _ := url.ParseRequestURI("https://test.example.com")
for _, rule := range rules { for _, rule := range rules {
authorizer := authorization.NewAuthorizer(schema.AccessControlConfiguration{ authorizer := authorization.NewAuthorizer(&schema.Configuration{
DefaultPolicy: "deny", AccessControl: schema.AccessControlConfiguration{
Rules: []schema.ACLRule{{ DefaultPolicy: "deny",
Domains: []string{"test.example.com"}, Rules: []schema.ACLRule{{
Policy: rule.Policy, Domains: []string{"test.example.com"},
}}, Policy: rule.Policy,
}) }},
}})
username := "" username := ""
if rule.AuthLevel > authentication.NotAuthenticated { if rule.AuthLevel > authentication.NotAuthenticated {
@ -181,16 +182,6 @@ func TestShouldVerifyWrongCredentials(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
} }
type TestCase struct {
URL string
Authorization string
ExpectedStatusCode int
}
func (tc TestCase) String() string {
return fmt.Sprintf("url=%s, auth=%s, exp_status=%d", tc.URL, tc.Authorization, tc.ExpectedStatusCode)
}
type BasicAuthorizationSuite struct { type BasicAuthorizationSuite struct {
suite.Suite suite.Suite
} }

View File

@ -105,7 +105,7 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
providers.Notifier = mockAuthelia.NotifierMock providers.Notifier = mockAuthelia.NotifierMock
providers.Authorizer = authorization.NewAuthorizer( providers.Authorizer = authorization.NewAuthorizer(
configuration.AccessControl) &configuration)
providers.SessionProvider = session.NewProvider( providers.SessionProvider = session.NewProvider(
configuration.Session, nil) configuration.Session, nil)

View File

@ -58,7 +58,7 @@ var redirectionAuthorizations = map[string]bool{
} }
func (s *RedirectionCheckScenario) TestShouldRedirectOnLoginOnlyWhenDomainIsSafe() { func (s *RedirectionCheckScenario) TestShouldRedirectOnLoginOnlyWhenDomainIsSafe() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 35*time.Second)
defer cancel() defer cancel()
secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password") secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password")
@ -66,12 +66,13 @@ func (s *RedirectionCheckScenario) TestShouldRedirectOnLoginOnlyWhenDomainIsSafe
for url, redirected := range redirectionAuthorizations { for url, redirected := range redirectionAuthorizations {
s.T().Run(url, func(t *testing.T) { s.T().Run(url, func(t *testing.T) {
s.doLoginTwoFactor(ctx, t, "john", "password", false, secret, url) s.doLoginTwoFactor(ctx, t, "john", "password", false, secret, url)
time.Sleep(1 * time.Second)
if redirected { if redirected {
s.verifySecretAuthorized(ctx, t) s.verifySecretAuthorized(ctx, t)
} else { } else {
s.verifyIsAuthenticatedPage(ctx, t) s.verifyIsAuthenticatedPage(ctx, t)
} }
s.doLogout(ctx, t) s.doLogout(ctx, t)
}) })
} }