feat(commands): add access-control check-policy command (#2871)

This adds an access-control command that checks the policy enforcement for a given criteria using a configuration file and refactors the configuration validation command to include all configuration sources.
pull/2927/head
James Elliott 2022-02-28 14:15:01 +11:00 committed by GitHub
parent d87a56fa1a
commit 3c81e75d79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1657 additions and 995 deletions

View File

@ -95,7 +95,7 @@ upgrading to prevent configuration changes from impacting downtime in an upgrade
integrations, it only checks that your configuration syntax is valid.
```console
$ authelia validate-config configuration.yml
$ authelia validate-config --config configuration.yml
```
# Duration Notation Format

View File

@ -124,12 +124,23 @@ func isMatchForNetworks(subject Subject, acl *AccessControlRule) (match bool) {
return false
}
// Same as isExactMatchForSubjects except it theoretically matches if subject is anonymous since they'd need to authenticate.
func isMatchForSubjects(subject Subject, acl *AccessControlRule) (match bool) {
// If there are no subjects in this rule then the subject condition is a match.
if len(acl.Subjects) == 0 || subject.IsAnonymous() {
if subject.IsAnonymous() {
return true
}
return isExactMatchForSubjects(subject, acl)
}
func isExactMatchForSubjects(subject Subject, acl *AccessControlRule) (match bool) {
// If there are no subjects in this rule then the subject condition is a match.
if len(acl.Subjects) == 0 {
return true
} else if subject.IsAnonymous() {
return false
}
// Iterate over the subjects until we find a match (return true) or until we exit the loop (return false).
for _, subjectRule := range acl.Subjects {
if subjectRule.IsMatch(subject) {

View File

@ -66,3 +66,28 @@ func (p Authorizer) GetRequiredLevel(subject Subject, object Object) Level {
return p.defaultPolicy
}
// GetRuleMatchResults iterates through the rules and produces a list of RuleMatchResult provided a subject and object.
func (p Authorizer) GetRuleMatchResults(subject Subject, object Object) (results []RuleMatchResult) {
skipped := false
results = make([]RuleMatchResult, len(p.rules))
for i, rule := range p.rules {
results[i] = RuleMatchResult{
Rule: rule,
Skipped: skipped,
MatchDomain: isMatchForDomains(subject, object, rule),
MatchResources: isMatchForResources(object, rule),
MatchMethods: isMatchForMethods(object, rule),
MatchNetworks: isMatchForNetworks(subject, rule),
MatchSubjects: isMatchForSubjects(subject, rule),
MatchSubjectsExact: isExactMatchForSubjects(subject, rule),
}
skipped = skipped || results[i].IsMatch()
}
return results
}

View File

@ -31,20 +31,23 @@ func NewAuthorizerTester(config schema.AccessControlConfiguration) *AuthorizerTe
}
func (s *AuthorizerTester) CheckAuthorizations(t *testing.T, subject Subject, requestURI, method string, expectedLevel Level) {
url, _ := url.ParseRequestURI(requestURI)
targetURL, _ := url.ParseRequestURI(requestURI)
object := Object{
Scheme: url.Scheme,
Domain: url.Hostname(),
Path: url.Path,
Method: method,
}
object := NewObject(targetURL, method)
level := s.GetRequiredLevel(subject, object)
assert.Equal(t, expectedLevel, level)
}
func (s *AuthorizerTester) GetRuleMatchResults(subject Subject, requestURI, method string) (results []RuleMatchResult) {
targetURL, _ := url.ParseRequestURI(requestURI)
object := NewObject(targetURL, method)
return s.Authorizer.GetRuleMatchResults(subject, object)
}
type AuthorizerTesterBuilder struct {
config schema.AccessControlConfiguration
}
@ -481,6 +484,59 @@ func (s *AuthorizerSuite) TestShouldMatchResourceWithSubjectRules() {
tester.CheckAuthorizations(s.T(), John, "https://private.example.com", "GET", TwoFactor)
tester.CheckAuthorizations(s.T(), Bob, "https://private.example.com", "GET", Denied)
tester.CheckAuthorizations(s.T(), AnonymousUser, "https://private.example.com", "GET", TwoFactor)
results := tester.GetRuleMatchResults(John, "https://private.example.com", "GET")
require.Len(s.T(), results, 7)
assert.False(s.T(), results[0].IsMatch())
assert.False(s.T(), results[0].MatchDomain)
assert.False(s.T(), results[0].MatchResources)
assert.True(s.T(), results[0].MatchSubjects)
assert.True(s.T(), results[0].MatchNetworks)
assert.True(s.T(), results[0].MatchMethods)
assert.False(s.T(), results[1].IsMatch())
assert.False(s.T(), results[1].MatchDomain)
assert.False(s.T(), results[1].MatchResources)
assert.True(s.T(), results[1].MatchSubjects)
assert.True(s.T(), results[1].MatchNetworks)
assert.True(s.T(), results[1].MatchMethods)
assert.False(s.T(), results[2].IsMatch())
assert.False(s.T(), results[2].MatchDomain)
assert.True(s.T(), results[2].MatchResources)
assert.True(s.T(), results[2].MatchSubjects)
assert.True(s.T(), results[2].MatchNetworks)
assert.True(s.T(), results[2].MatchMethods)
assert.False(s.T(), results[3].IsMatch())
assert.False(s.T(), results[3].MatchDomain)
assert.False(s.T(), results[3].MatchResources)
assert.True(s.T(), results[3].MatchSubjects)
assert.True(s.T(), results[3].MatchNetworks)
assert.True(s.T(), results[3].MatchMethods)
assert.False(s.T(), results[4].IsMatch())
assert.False(s.T(), results[4].MatchDomain)
assert.False(s.T(), results[4].MatchResources)
assert.True(s.T(), results[4].MatchSubjects)
assert.True(s.T(), results[4].MatchNetworks)
assert.True(s.T(), results[4].MatchMethods)
assert.False(s.T(), results[5].IsMatch())
assert.False(s.T(), results[5].MatchDomain)
assert.True(s.T(), results[5].MatchResources)
assert.True(s.T(), results[5].MatchSubjects)
assert.True(s.T(), results[5].MatchNetworks)
assert.True(s.T(), results[5].MatchMethods)
assert.True(s.T(), results[6].IsMatch())
assert.True(s.T(), results[6].MatchDomain)
assert.True(s.T(), results[6].MatchResources)
assert.True(s.T(), results[6].MatchSubjects)
assert.True(s.T(), results[6].MatchNetworks)
assert.True(s.T(), results[6].MatchMethods)
}
func (s *AuthorizerSuite) TestPolicyToLevel() {

View File

@ -58,3 +58,27 @@ func NewObject(targetURL *url.URL, method string) (object Object) {
return object
}
// RuleMatchResult describes how well a rule matched a subject/object combo.
type RuleMatchResult struct {
Rule *AccessControlRule
Skipped bool
MatchDomain bool
MatchResources bool
MatchMethods bool
MatchNetworks bool
MatchSubjects bool
MatchSubjectsExact bool
}
// IsMatch returns true if all the criteria matched.
func (r RuleMatchResult) IsMatch() (match bool) {
return r.MatchDomain && r.MatchResources && r.MatchMethods && r.MatchNetworks && r.MatchSubjectsExact
}
// IsPotentialMatch returns true if the rule is potentially a match.
func (r RuleMatchResult) IsPotentialMatch() (match bool) {
return r.MatchDomain && r.MatchResources && r.MatchMethods && r.MatchNetworks && r.MatchSubjects && !r.MatchSubjectsExact
}

View File

@ -25,6 +25,22 @@ func PolicyToLevel(policy string) Level {
return Denied
}
// LevelToPolicy converts a int authorization level to string policy.
func LevelToPolicy(level Level) (policy string) {
switch level {
case Bypass:
return bypass
case OneFactor:
return oneFactor
case TwoFactor:
return twoFactor
case Denied:
return deny
}
return deny
}
func schemaSubjectToACLSubject(subjectRule string) (subject AccessControlSubject) {
if strings.HasPrefix(subjectRule, userPrefix) {
user := strings.Trim(subjectRule[len(userPrefix):], " ")

View File

@ -0,0 +1,242 @@
package commands
import (
"errors"
"fmt"
"net"
"net/url"
"strings"
"github.com/spf13/cobra"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/configuration"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/configuration/validator"
)
func newAccessControlCommand() (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: "access-control",
Short: "Helpers for the access control system",
}
cmd.AddCommand(
newAccessControlCheckCommand(),
)
return cmd
}
func newAccessControlCheckCommand() (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: "check-policy",
Short: "Checks a request against the access control rules to determine what policy would be applied",
Long: accessControlPolicyCheckLong,
RunE: accessControlCheckRunE,
}
cmdWithConfigFlags(cmd, false, []string{"config.yml"})
cmd.Flags().String("url", "", "the url of the object")
cmd.Flags().String("method", "GET", "the HTTP method of the object")
cmd.Flags().String("username", "", "the username of the subject")
cmd.Flags().StringSlice("groups", nil, "the groups of the subject")
cmd.Flags().String("ip", "", "the ip of the subject")
cmd.Flags().Bool("verbose", false, "enables verbose output")
return cmd
}
func accessControlCheckRunE(cmd *cobra.Command, _ []string) (err error) {
configs, err := cmd.Flags().GetStringSlice("config")
if err != nil {
return err
}
sources := make([]configuration.Source, len(configs)+2)
for i, path := range configs {
sources[i] = configuration.NewYAMLFileSource(path)
}
sources[0+len(configs)] = configuration.NewEnvironmentSource(configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)
sources[1+len(configs)] = configuration.NewSecretsSource(configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)
val := schema.NewStructValidator()
accessControlConfig := &schema.Configuration{}
if _, err = configuration.LoadAdvanced(val, "access_control", &accessControlConfig.AccessControl, sources...); err != nil {
return err
}
v := schema.NewStructValidator()
validator.ValidateAccessControl(accessControlConfig, v)
if v.HasErrors() || v.HasWarnings() {
return errors.New("your configuration has errors")
}
authorizer := authorization.NewAuthorizer(accessControlConfig)
subject, object, err := getSubjectAndObjectFromFlags(cmd)
if err != nil {
return err
}
results := authorizer.GetRuleMatchResults(subject, object)
if len(results) == 0 {
fmt.Printf("\nThe default policy '%s' will be applied to ALL requests as no rules are configured.\n\n", accessControlConfig.AccessControl.DefaultPolicy)
return nil
}
verbose, err := cmd.Flags().GetBool("verbose")
if err != nil {
return err
}
accessControlCheckWriteOutput(object, subject, results, accessControlConfig.AccessControl.DefaultPolicy, verbose)
return nil
}
func accessControlCheckWriteObjectSubject(object authorization.Object, subject authorization.Subject) {
output := strings.Builder{}
output.WriteString(fmt.Sprintf("Performing policy check for request to '%s'", object.String()))
if object.Method != "" {
output.WriteString(fmt.Sprintf(" method '%s'", object.Method))
}
if subject.Username != "" {
output.WriteString(fmt.Sprintf(" username '%s'", subject.Username))
}
if len(subject.Groups) != 0 {
output.WriteString(fmt.Sprintf(" groups '%s'", strings.Join(subject.Groups, ",")))
}
if subject.IP != nil {
output.WriteString(fmt.Sprintf(" from IP '%s'", subject.IP.String()))
}
output.WriteString(".\n")
fmt.Println(output.String())
}
func accessControlCheckWriteOutput(object authorization.Object, subject authorization.Subject, results []authorization.RuleMatchResult, defaultPolicy string, verbose bool) {
accessControlCheckWriteObjectSubject(object, subject)
fmt.Printf(" #\tDomain\tResource\tMethod\tNetwork\tSubject\n")
var (
appliedPos int
applied authorization.RuleMatchResult
potentialPos int
potential authorization.RuleMatchResult
)
for i, result := range results {
if result.Skipped && !verbose {
break
}
switch {
case result.IsMatch() && !result.Skipped:
appliedPos, applied = i+1, result
fmt.Printf("* %d\t%s\t%s\t\t%s\t%s\t%s\n", i+1, hitMissMay(result.MatchDomain), hitMissMay(result.MatchResources), hitMissMay(result.MatchMethods), hitMissMay(result.MatchNetworks), hitMissMay(result.MatchSubjects, result.MatchSubjectsExact))
case result.IsPotentialMatch() && !result.Skipped:
if potentialPos == 0 {
potentialPos, potential = i+1, result
}
fmt.Printf("~ %d\t%s\t%s\t\t%s\t%s\t%s\n", i+1, hitMissMay(result.MatchDomain), hitMissMay(result.MatchResources), hitMissMay(result.MatchMethods), hitMissMay(result.MatchNetworks), hitMissMay(result.MatchSubjects, result.MatchSubjectsExact))
default:
fmt.Printf(" %d\t%s\t%s\t\t%s\t%s\t%s\n", i+1, hitMissMay(result.MatchDomain), hitMissMay(result.MatchResources), hitMissMay(result.MatchMethods), hitMissMay(result.MatchNetworks), hitMissMay(result.MatchSubjects, result.MatchSubjectsExact))
}
}
switch {
case appliedPos != 0 && (potentialPos == 0 || (potentialPos > appliedPos)):
fmt.Printf("\nThe policy '%s' from rule #%d will be applied to this request.\n\n", authorization.LevelToPolicy(applied.Rule.Policy), appliedPos)
case potentialPos != 0 && appliedPos != 0:
fmt.Printf("\nThe policy '%s' from rule #%d will potentially be applied to this request. If not policy '%s' from rule #%d will be.\n\n", authorization.LevelToPolicy(potential.Rule.Policy), potentialPos, authorization.LevelToPolicy(applied.Rule.Policy), appliedPos)
case potentialPos != 0:
fmt.Printf("\nThe policy '%s' from rule #%d will potentially be applied to this request. Otherwise the policy '%s' from the default policy will be.\n\n", authorization.LevelToPolicy(potential.Rule.Policy), potentialPos, defaultPolicy)
default:
fmt.Printf("\nThe policy '%s' from the default policy will be applied to this request as no rules matched the request.\n\n", defaultPolicy)
}
}
func hitMissMay(in ...bool) (out string) {
var hit, miss bool
for _, x := range in {
if x {
hit = true
} else {
miss = true
}
}
switch {
case hit && miss:
return "may"
case hit:
return "hit"
default:
return "miss"
}
}
func getSubjectAndObjectFromFlags(cmd *cobra.Command) (subject authorization.Subject, object authorization.Object, err error) {
requestURL, err := cmd.Flags().GetString("url")
if err != nil {
return subject, object, err
}
parsedURL, err := url.Parse(requestURL)
if err != nil {
return subject, object, err
}
method, err := cmd.Flags().GetString("method")
if err != nil {
return subject, object, err
}
username, err := cmd.Flags().GetString("username")
if err != nil {
return subject, object, err
}
groups, err := cmd.Flags().GetStringSlice("groups")
if err != nil {
return subject, object, err
}
remoteIP, err := cmd.Flags().GetString("ip")
if err != nil {
return subject, object, err
}
parsedIP := net.ParseIP(remoteIP)
subject = authorization.Subject{
Username: username,
Groups: groups,
IP: parsedIP,
}
object = authorization.NewObject(parsedURL, method)
return subject, object, nil
}

View File

@ -3,6 +3,7 @@ package commands
import (
"os"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/authelia/authelia/v4/internal/configuration"
@ -12,18 +13,27 @@ import (
)
// cmdWithConfigFlags is used for commands which require access to the configuration to add the flag to the command.
func cmdWithConfigFlags(cmd *cobra.Command) {
cmd.Flags().StringSliceP("config", "c", []string{}, "Configuration files")
func cmdWithConfigFlags(cmd *cobra.Command, persistent bool, configs []string) {
if persistent {
cmd.PersistentFlags().StringSliceP("config", "c", configs, "configuration files to load")
} else {
cmd.Flags().StringSliceP("config", "c", configs, "configuration files to load")
}
}
var config *schema.Configuration
func newCmdWithConfigPreRun(ensureConfigExists, validateKeys, validateConfiguration bool) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, _ []string) {
logger := logging.Logger()
var (
logger *logrus.Logger
configs []string
err error
)
configs, err := cmd.Root().Flags().GetStringSlice("config")
if err != nil {
logger = logging.Logger()
if configs, err = cmd.Flags().GetStringSlice("config"); err != nil {
logger.Fatalf("Error reading flags: %v", err)
}
@ -39,23 +49,15 @@ func newCmdWithConfigPreRun(ensureConfigExists, validateKeys, validateConfigurat
}
}
var keys []string
var (
val *schema.StructValidator
)
val := schema.NewStructValidator()
keys, config, err = configuration.Load(val, configuration.NewDefaultSources(configs, configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)...)
config, val, err = loadConfig(configs, validateKeys, validateConfiguration)
if err != nil {
logger.Fatalf("Error occurred loading configuration: %v", err)
}
if validateKeys {
validator.ValidateKeys(keys, configuration.DefaultEnvPrefix, val)
}
if validateConfiguration {
validator.ValidateConfiguration(config, val)
}
warnings := val.Warnings()
if len(warnings) != 0 {
for _, warning := range warnings {
@ -73,3 +75,27 @@ func newCmdWithConfigPreRun(ensureConfigExists, validateKeys, validateConfigurat
}
}
}
func loadConfig(configs []string, validateKeys, validateConfiguration bool) (c *schema.Configuration, val *schema.StructValidator, err error) {
var keys []string
val = schema.NewStructValidator()
if keys, c, err = configuration.Load(val,
configuration.NewDefaultSources(
configs,
configuration.DefaultEnvPrefix,
configuration.DefaultEnvDelimiter)...); err != nil {
return nil, nil, err
}
if validateKeys {
validator.ValidateKeys(keys, configuration.DefaultEnvPrefix, val)
}
if validateConfiguration {
validator.ValidateConfiguration(c, val)
}
return c, val, nil
}

View File

@ -80,6 +80,23 @@ PowerShell:
# and source this file from your PowerShell profile.
`
const accessControlPolicyCheckLong = `
Checks a request against the access control rules to determine what policy would be applied.
Legend:
# The rule position in the configuration.
* The first fully matched rule.
~ Potential match i.e. if the user was authenticated they may match this rule.
hit The criteria in this column is a match to the request.
miss The criteria in this column is not match to the request.
may The criteria in this column is potentially a match to the request.
Notes:
A rule that potentially matches a request will cause a redirection to occur in order to perform one-factor
authentication. This is so Authelia can adequately determine if the rule actually matches.
`
const (
storageMigrateDirectionUp = "up"
storageMigrateDirectionDown = "down"

View File

@ -31,7 +31,7 @@ func NewRootCmd() (cmd *cobra.Command) {
Run: cmdRootRun,
}
cmdWithConfigFlags(cmd)
cmdWithConfigFlags(cmd, false, []string{})
cmd.AddCommand(
newBuildInfoCmd(),
@ -41,6 +41,7 @@ func NewRootCmd() (cmd *cobra.Command) {
NewRSACmd(),
NewStorageCmd(),
newValidateConfigCmd(),
newAccessControlCommand(),
)
return cmd

View File

@ -13,7 +13,7 @@ func NewStorageCmd() (cmd *cobra.Command) {
PersistentPreRunE: storagePersistentPreRunE,
}
cmd.PersistentFlags().StringSliceP("config", "c", []string{"config.yml"}, "configuration file to load for the storage migration")
cmdWithConfigFlags(cmd, true, []string{"config.yml"})
cmd.PersistentFlags().String("encryption-key", "", "the storage encryption key to use")

View File

@ -1,66 +1,70 @@
package commands
import (
"log"
"os"
"fmt"
"github.com/spf13/cobra"
"github.com/authelia/authelia/v4/internal/configuration"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/configuration/validator"
"github.com/authelia/authelia/v4/internal/logging"
)
func newValidateConfigCmd() (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: "validate-config [yaml]",
Use: "validate-config",
Short: "Check a configuration against the internal configuration validation mechanisms",
Args: cobra.MinimumNArgs(1),
Run: cmdValidateConfigRun,
Args: cobra.NoArgs,
RunE: cmdValidateConfigRunE,
}
cmdWithConfigFlags(cmd, false, []string{"config.yml"})
return cmd
}
func cmdValidateConfigRun(_ *cobra.Command, args []string) {
logger := logging.Logger()
func cmdValidateConfigRunE(cmd *cobra.Command, _ []string) (err error) {
var (
configs []string
val *schema.StructValidator
)
configPath := args[0]
if _, err := os.Stat(configPath); err != nil {
logger.Fatalf("Error Loading Configuration: %v\n", err)
if configs, err = cmd.Flags().GetStringSlice("config"); err != nil {
return err
}
val := schema.NewStructValidator()
keys, conf, err := configuration.Load(val, configuration.NewYAMLFileSource(configPath))
config, val, err = loadConfig(configs, true, true)
if err != nil {
logger.Fatalf("Error occurred loading configuration: %v", err)
return fmt.Errorf("error occurred loading configuration: %v", err)
}
validator.ValidateKeys(keys, configuration.DefaultEnvPrefix, val)
validator.ValidateConfiguration(conf, val)
switch {
case val.HasErrors():
fmt.Println("Configuration parsed and loaded with errors:")
fmt.Println("")
warnings := val.Warnings()
errors := val.Errors()
if len(warnings) != 0 {
logger.Warn("Warnings occurred while loading the configuration:")
for _, warn := range warnings {
logger.Warnf(" %+v", warn)
}
}
if len(errors) != 0 {
logger.Error("Errors occurred while loading the configuration:")
for _, err := range errors {
logger.Errorf(" %+v", err)
for _, err = range val.Errors() {
fmt.Printf("\t - %v\n", err)
}
logger.Fatal("Can't continue due to errors")
fmt.Println("")
if !val.HasWarnings() {
break
}
fallthrough
case val.HasWarnings():
fmt.Println("Configuration parsed and loaded with warnings:")
fmt.Println("")
for _, err = range val.Warnings() {
fmt.Printf("\t - %v\n", err)
}
fmt.Println("")
default:
fmt.Println("Configuration parsed and loaded successfully without errors.")
fmt.Println("")
}
log.Println("Configuration parsed successfully without errors.")
return nil
}

View File

@ -260,7 +260,7 @@ func TestShouldHandleErrInvalidatorWhenSMTPSenderBlank(t *testing.T) {
require.Len(t, val.Errors(), 1)
assert.Len(t, val.Warnings(), 0)
assert.EqualError(t, val.Errors()[0], "smtp notifier: the 'sender' must be configured")
assert.EqualError(t, val.Errors()[0], "notifier: smtp: option 'sender' is required")
}
func TestShouldDecodeSMTPSenderWithoutName(t *testing.T) {

View File

@ -12,7 +12,7 @@ import (
// IsPolicyValid check if policy is valid.
func IsPolicyValid(policy string) (isValid bool) {
return policy == policyDeny || policy == policyOneFactor || policy == policyTwoFactor || policy == policyBypass
return utils.IsStringInSlice(policy, validACLRulePolicies)
}
// IsResourceValid check if a resource is valid.
@ -27,8 +27,8 @@ func IsSubjectValid(subject string) (isValid bool) {
}
// IsNetworkGroupValid check if a network group is valid.
func IsNetworkGroupValid(configuration schema.AccessControlConfiguration, network string) bool {
for _, networks := range configuration.Networks {
func IsNetworkGroupValid(config schema.AccessControlConfiguration, network string) bool {
for _, networks := range config.Networks {
if network != networks.Name {
continue
} else {
@ -49,21 +49,29 @@ func IsNetworkValid(network string) (isValid bool) {
return true
}
func ruleDescriptor(position int, rule schema.ACLRule) string {
if len(rule.Domains) == 0 {
return fmt.Sprintf("#%d", position)
}
return fmt.Sprintf("#%d (domain '%s')", position, strings.Join(rule.Domains, ","))
}
// ValidateAccessControl validates access control configuration.
func ValidateAccessControl(configuration *schema.AccessControlConfiguration, validator *schema.StructValidator) {
if configuration.DefaultPolicy == "" {
configuration.DefaultPolicy = policyDeny
func ValidateAccessControl(config *schema.Configuration, validator *schema.StructValidator) {
if config.AccessControl.DefaultPolicy == "" {
config.AccessControl.DefaultPolicy = policyDeny
}
if !IsPolicyValid(configuration.DefaultPolicy) {
validator.Push(fmt.Errorf("'default_policy' must either be 'deny', 'two_factor', 'one_factor' or 'bypass'"))
if !IsPolicyValid(config.AccessControl.DefaultPolicy) {
validator.Push(fmt.Errorf(errFmtAccessControlDefaultPolicyValue, strings.Join(validACLRulePolicies, "', '"), config.AccessControl.DefaultPolicy))
}
if configuration.Networks != nil {
for _, n := range configuration.Networks {
if config.AccessControl.Networks != nil {
for _, n := range config.AccessControl.Networks {
for _, networks := range n.Networks {
if !IsNetworkValid(networks) {
validator.Push(fmt.Errorf("Network %s from network group: %s must be a valid IP or CIDR", n.Networks, n.Name))
validator.Push(fmt.Errorf(errFmtAccessControlNetworkGroupIPCIDRInvalid, n.Name, networks))
}
}
}
@ -71,31 +79,31 @@ func ValidateAccessControl(configuration *schema.AccessControlConfiguration, val
}
// ValidateRules validates an ACL Rule configuration.
func ValidateRules(configuration schema.AccessControlConfiguration, validator *schema.StructValidator) {
if configuration.Rules == nil || len(configuration.Rules) == 0 {
if configuration.DefaultPolicy != policyOneFactor && configuration.DefaultPolicy != policyTwoFactor {
validator.Push(fmt.Errorf("Default Policy [%s] is invalid, access control rules must be provided or a policy must either be 'one_factor' or 'two_factor'", configuration.DefaultPolicy))
func ValidateRules(config *schema.Configuration, validator *schema.StructValidator) {
if config.AccessControl.Rules == nil || len(config.AccessControl.Rules) == 0 {
if config.AccessControl.DefaultPolicy != policyOneFactor && config.AccessControl.DefaultPolicy != policyTwoFactor {
validator.Push(fmt.Errorf(errFmtAccessControlDefaultPolicyWithoutRules, config.AccessControl.DefaultPolicy))
return
}
validator.PushWarning(fmt.Errorf("No access control rules have been defined so the default policy %s will be applied to all requests", configuration.DefaultPolicy))
validator.PushWarning(fmt.Errorf(errFmtAccessControlWarnNoRulesDefaultPolicy, config.AccessControl.DefaultPolicy))
return
}
for i, rule := range configuration.Rules {
for i, rule := range config.AccessControl.Rules {
rulePosition := i + 1
if len(rule.Domains) == 0 {
validator.Push(fmt.Errorf("Rule #%d is invalid, a policy must have one or more domains", rulePosition))
validator.Push(fmt.Errorf(errFmtAccessControlRuleNoDomains, ruleDescriptor(rulePosition, rule)))
}
if !IsPolicyValid(rule.Policy) {
validator.Push(fmt.Errorf("Policy [%s] for rule #%d domain: %s is invalid, a policy must either be 'deny', 'two_factor', 'one_factor' or 'bypass'", rule.Policy, rulePosition, rule.Domains))
validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidPolicy, ruleDescriptor(rulePosition, rule), rule.Policy))
}
validateNetworks(rulePosition, rule, configuration, validator)
validateNetworks(rulePosition, rule, config.AccessControl, validator)
validateResources(rulePosition, rule, validator)
@ -104,16 +112,16 @@ func ValidateRules(configuration schema.AccessControlConfiguration, validator *s
validateMethods(rulePosition, rule, validator)
if rule.Policy == policyBypass && len(rule.Subjects) != 0 {
validator.Push(fmt.Errorf(errAccessControlInvalidPolicyWithSubjects, rulePosition, rule.Domains, rule.Subjects))
validator.Push(fmt.Errorf(errAccessControlRuleBypassPolicyInvalidWithSubjects, ruleDescriptor(rulePosition, rule)))
}
}
}
func validateNetworks(rulePosition int, rule schema.ACLRule, configuration schema.AccessControlConfiguration, validator *schema.StructValidator) {
func validateNetworks(rulePosition int, rule schema.ACLRule, config schema.AccessControlConfiguration, validator *schema.StructValidator) {
for _, network := range rule.Networks {
if !IsNetworkValid(network) {
if !IsNetworkGroupValid(configuration, network) {
validator.Push(fmt.Errorf("Network %s for rule #%d domain: %s is not a valid network or network group", rule.Networks, rulePosition, rule.Domains))
if !IsNetworkGroupValid(config, network) {
validator.Push(fmt.Errorf(errFmtAccessControlRuleNetworksInvalid, ruleDescriptor(rulePosition, rule), network))
}
}
}
@ -122,7 +130,7 @@ func validateNetworks(rulePosition int, rule schema.ACLRule, configuration schem
func validateResources(rulePosition int, rule schema.ACLRule, validator *schema.StructValidator) {
for _, resource := range rule.Resources {
if err := IsResourceValid(resource); err != nil {
validator.Push(fmt.Errorf("Resource %s for rule #%d domain: %s is invalid, %s", rule.Resources, rulePosition, rule.Domains, err))
validator.Push(fmt.Errorf(errFmtAccessControlRuleResourceInvalid, ruleDescriptor(rulePosition, rule), resource, err))
}
}
}
@ -131,7 +139,7 @@ func validateSubjects(rulePosition int, rule schema.ACLRule, validator *schema.S
for _, subjectRule := range rule.Subjects {
for _, subject := range subjectRule {
if !IsSubjectValid(subject) {
validator.Push(fmt.Errorf("Subject %s for rule #%d domain: %s is invalid, must start with 'user:' or 'group:'", subjectRule, rulePosition, rule.Domains))
validator.Push(fmt.Errorf(errFmtAccessControlRuleSubjectInvalid, ruleDescriptor(rulePosition, rule), subject))
}
}
}
@ -139,8 +147,8 @@ func validateSubjects(rulePosition int, rule schema.ACLRule, validator *schema.S
func validateMethods(rulePosition int, rule schema.ACLRule, validator *schema.StructValidator) {
for _, method := range rule.Methods {
if !utils.IsStringInSliceFold(method, validHTTPRequestMethods) {
validator.Push(fmt.Errorf("Method %s for rule #%d domain: %s is invalid, must be one of the following methods: %s", method, rulePosition, rule.Domains, strings.Join(validHTTPRequestMethods, ", ")))
if !utils.IsStringInSliceFold(method, validACLRuleMethods) {
validator.Push(fmt.Errorf(errFmtAccessControlRuleMethodInvalid, ruleDescriptor(rulePosition, rule), method, strings.Join(validACLRuleMethods, "', '")))
}
}
}

View File

@ -12,107 +12,117 @@ import (
type AccessControl struct {
suite.Suite
configuration schema.AccessControlConfiguration
validator *schema.StructValidator
config *schema.Configuration
validator *schema.StructValidator
}
func (suite *AccessControl) SetupTest() {
suite.validator = schema.NewStructValidator()
suite.configuration.DefaultPolicy = policyDeny
suite.configuration.Networks = schema.DefaultACLNetwork
suite.configuration.Rules = schema.DefaultACLRule
suite.config = &schema.Configuration{
AccessControl: schema.AccessControlConfiguration{
DefaultPolicy: policyDeny,
Networks: schema.DefaultACLNetwork,
Rules: schema.DefaultACLRule,
},
}
}
func (suite *AccessControl) TestShouldValidateCompleteConfiguration() {
ValidateAccessControl(&suite.configuration, suite.validator)
ValidateAccessControl(suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() {
suite.configuration.DefaultPolicy = testInvalidPolicy
suite.config.AccessControl.DefaultPolicy = testInvalidPolicy
ValidateAccessControl(&suite.configuration, suite.validator)
ValidateAccessControl(suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "'default_policy' must either be 'deny', 'two_factor', 'one_factor' or 'bypass'")
suite.Assert().EqualError(suite.validator.Errors()[0], "access control: option 'default_policy' must be one of 'bypass', 'one_factor', 'two_factor', 'deny' but it is configured as 'invalid'")
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidNetworkGroupNetwork() {
suite.configuration.Networks = []schema.ACLNetwork{
suite.config.AccessControl.Networks = []schema.ACLNetwork{
{
Name: "internal",
Networks: []string{"abc.def.ghi.jkl"},
},
}
ValidateAccessControl(&suite.configuration, suite.validator)
ValidateAccessControl(suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Network [abc.def.ghi.jkl] from network group: internal must be a valid IP or CIDR")
suite.Assert().EqualError(suite.validator.Errors()[0], "access control: networks: network group 'internal' is invalid: the network 'abc.def.ghi.jkl' is not a valid IP or CIDR notation")
}
func (suite *AccessControl) TestShouldRaiseErrorWithNoRulesDefined() {
suite.configuration.Rules = []schema.ACLRule{}
suite.config.AccessControl.Rules = []schema.ACLRule{}
ValidateRules(suite.configuration, suite.validator)
ValidateRules(suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Default Policy [deny] is invalid, access control rules must be provided or a policy must either be 'one_factor' or 'two_factor'")
suite.Assert().EqualError(suite.validator.Errors()[0], "access control: 'default_policy' option 'deny' is invalid: when no rules are specified it must be 'two_factor' or 'one_factor'")
}
func (suite *AccessControl) TestShouldRaiseWarningWithNoRulesDefined() {
suite.configuration.Rules = []schema.ACLRule{}
suite.config.AccessControl.Rules = []schema.ACLRule{}
suite.configuration.DefaultPolicy = policyTwoFactor
suite.config.AccessControl.DefaultPolicy = policyTwoFactor
ValidateRules(suite.configuration, suite.validator)
ValidateRules(suite.config, suite.validator)
suite.Assert().False(suite.validator.HasErrors())
suite.Require().Len(suite.validator.Warnings(), 1)
suite.Assert().EqualError(suite.validator.Warnings()[0], "No access control rules have been defined so the default policy two_factor will be applied to all requests")
suite.Assert().EqualError(suite.validator.Warnings()[0], "access control: no rules have been specified so the 'default_policy' of 'two_factor' is going to be applied to all requests")
}
func (suite *AccessControl) TestShouldRaiseErrorsWithEmptyRules() {
suite.configuration.Rules = []schema.ACLRule{{}, {}}
suite.config.AccessControl.Rules = []schema.ACLRule{
{},
{
Policy: "wrong",
},
}
ValidateRules(suite.configuration, suite.validator)
ValidateRules(suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 4)
suite.Assert().EqualError(suite.validator.Errors()[0], "Rule #1 is invalid, a policy must have one or more domains")
suite.Assert().EqualError(suite.validator.Errors()[1], "Policy [] for rule #1 domain: [] is invalid, a policy must either be 'deny', 'two_factor', 'one_factor' or 'bypass'")
suite.Assert().EqualError(suite.validator.Errors()[2], "Rule #2 is invalid, a policy must have one or more domains")
suite.Assert().EqualError(suite.validator.Errors()[3], "Policy [] for rule #2 domain: [] is invalid, a policy must either be 'deny', 'two_factor', 'one_factor' or 'bypass'")
suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1: rule is invalid: must have the option 'domain' configured")
suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #1: rule 'policy' option '' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'")
suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #2: rule is invalid: must have the option 'domain' configured")
suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #2: rule 'policy' option 'wrong' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'")
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() {
suite.configuration.Rules = []schema.ACLRule{
suite.config.AccessControl.Rules = []schema.ACLRule{
{
Domains: []string{"public.example.com"},
Policy: testInvalidPolicy,
},
}
ValidateRules(suite.configuration, suite.validator)
ValidateRules(suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Policy [invalid] for rule #1 domain: [public.example.com] is invalid, a policy must either be 'deny', 'two_factor', 'one_factor' or 'bypass'")
suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): rule 'policy' option 'invalid' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'")
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidNetwork() {
suite.configuration.Rules = []schema.ACLRule{
suite.config.AccessControl.Rules = []schema.ACLRule{
{
Domains: []string{"public.example.com"},
Policy: "bypass",
@ -120,16 +130,16 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidNetwork() {
},
}
ValidateRules(suite.configuration, suite.validator)
ValidateRules(suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Network [abc.def.ghi.jkl/32] for rule #1 domain: [public.example.com] is not a valid network or network group")
suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): the network 'abc.def.ghi.jkl/32' is not a valid Group Name, IP, or CIDR notation")
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidMethod() {
suite.configuration.Rules = []schema.ACLRule{
suite.config.AccessControl.Rules = []schema.ACLRule{
{
Domains: []string{"public.example.com"},
Policy: "bypass",
@ -137,16 +147,16 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidMethod() {
},
}
ValidateRules(suite.configuration, suite.validator)
ValidateRules(suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Method HOP for rule #1 domain: [public.example.com] is invalid, must be one of the following methods: GET, HEAD, POST, PUT, PATCH, DELETE, TRACE, CONNECT, OPTIONS")
suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'methods' option 'HOP' is invalid: must be one of 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS'")
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidResource() {
suite.configuration.Rules = []schema.ACLRule{
suite.config.AccessControl.Rules = []schema.ACLRule{
{
Domains: []string{"public.example.com"},
Policy: "bypass",
@ -154,18 +164,18 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidResource() {
},
}
ValidateRules(suite.configuration, suite.validator)
ValidateRules(suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Resource [^/(api.*] for rule #1 domain: [public.example.com] is invalid, error parsing regexp: missing closing ): `^/(api.*`")
suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'resources' option '^/(api.*' is invalid: error parsing regexp: missing closing ): `^/(api.*`")
}
func (suite *AccessControl) TestShouldRaiseErrorInvalidSubject() {
domains := []string{"public.example.com"}
subjects := [][]string{{"invalid"}}
suite.configuration.Rules = []schema.ACLRule{
suite.config.AccessControl.Rules = []schema.ACLRule{
{
Domains: domains,
Policy: "bypass",
@ -173,13 +183,13 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidSubject() {
},
}
ValidateRules(suite.configuration, suite.validator)
ValidateRules(suite.config, suite.validator)
suite.Require().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 2)
suite.Assert().EqualError(suite.validator.Errors()[0], "Subject [invalid] for rule #1 domain: [public.example.com] is invalid, must start with 'user:' or 'group:'")
suite.Assert().EqualError(suite.validator.Errors()[1], fmt.Sprintf(errAccessControlInvalidPolicyWithSubjects, 1, domains, subjects))
suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'subject' option 'invalid' is invalid: must start with 'user:' or 'group:'")
suite.Assert().EqualError(suite.validator.Errors()[1], fmt.Sprintf(errAccessControlRuleBypassPolicyInvalidWithSubjects, ruleDescriptor(1, suite.config.AccessControl.Rules[0])))
}
func TestAccessControl(t *testing.T) {

View File

@ -1,7 +1,6 @@
package validator
import (
"errors"
"fmt"
"net/url"
"strings"
@ -11,260 +10,254 @@ import (
)
// ValidateAuthenticationBackend validates and updates the authentication backend configuration.
func ValidateAuthenticationBackend(configuration *schema.AuthenticationBackendConfiguration, validator *schema.StructValidator) {
if configuration.LDAP == nil && configuration.File == nil {
validator.Push(errors.New("Please provide `ldap` or `file` object in `authentication_backend`"))
func ValidateAuthenticationBackend(config *schema.AuthenticationBackendConfiguration, validator *schema.StructValidator) {
if config.LDAP == nil && config.File == nil {
validator.Push(fmt.Errorf(errFmtAuthBackendNotConfigured))
}
if configuration.LDAP != nil && configuration.File != nil {
validator.Push(errors.New("You cannot provide both `ldap` and `file` objects in `authentication_backend`"))
if config.LDAP != nil && config.File != nil {
validator.Push(fmt.Errorf(errFmtAuthBackendMultipleConfigured))
}
if configuration.File != nil {
validateFileAuthenticationBackend(configuration.File, validator)
} else if configuration.LDAP != nil {
validateLDAPAuthenticationBackend(configuration.LDAP, validator)
if config.File != nil {
validateFileAuthenticationBackend(config.File, validator)
} else if config.LDAP != nil {
validateLDAPAuthenticationBackend(config.LDAP, validator)
}
if configuration.RefreshInterval == "" {
configuration.RefreshInterval = schema.RefreshIntervalDefault
if config.RefreshInterval == "" {
config.RefreshInterval = schema.RefreshIntervalDefault
} else {
_, err := utils.ParseDurationString(configuration.RefreshInterval)
if err != nil && configuration.RefreshInterval != schema.ProfileRefreshDisabled && configuration.RefreshInterval != schema.ProfileRefreshAlways {
validator.Push(fmt.Errorf("Auth Backend `refresh_interval` is configured to '%s' but it must be either a duration notation or one of 'disable', or 'always'. Error from parser: %s", configuration.RefreshInterval, err))
_, err := utils.ParseDurationString(config.RefreshInterval)
if err != nil && config.RefreshInterval != schema.ProfileRefreshDisabled && config.RefreshInterval != schema.ProfileRefreshAlways {
validator.Push(fmt.Errorf(errFmtAuthBackendRefreshInterval, config.RefreshInterval, err))
}
}
}
// validateFileAuthenticationBackend validates and updates the file authentication backend configuration.
func validateFileAuthenticationBackend(configuration *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
if configuration.Path == "" {
validator.Push(errors.New("Please provide a `path` for the users database in `authentication_backend`"))
func validateFileAuthenticationBackend(config *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
if config.Path == "" {
validator.Push(fmt.Errorf(errFmtFileAuthBackendPathNotConfigured))
}
if configuration.Password == nil {
configuration.Password = &schema.DefaultPasswordConfiguration
if config.Password == nil {
config.Password = &schema.DefaultPasswordConfiguration
} else {
// Salt Length.
switch {
case configuration.Password.SaltLength == 0:
configuration.Password.SaltLength = schema.DefaultPasswordConfiguration.SaltLength
case configuration.Password.SaltLength < 8:
validator.Push(fmt.Errorf("The salt length must be 2 or more, you configured %d", configuration.Password.SaltLength))
case config.Password.SaltLength == 0:
config.Password.SaltLength = schema.DefaultPasswordConfiguration.SaltLength
case config.Password.SaltLength < 8:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordSaltLength, config.Password.SaltLength))
}
switch configuration.Password.Algorithm {
switch config.Password.Algorithm {
case "":
configuration.Password.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
config.Password.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
fallthrough
case hashArgon2id:
validateFileAuthenticationBackendArgon2id(configuration, validator)
validateFileAuthenticationBackendArgon2id(config, validator)
case hashSHA512:
validateFileAuthenticationBackendSHA512(configuration)
validateFileAuthenticationBackendSHA512(config)
default:
validator.Push(fmt.Errorf("Unknown hashing algorithm supplied, valid values are argon2id and sha512, you configured '%s'", configuration.Password.Algorithm))
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Password.Algorithm))
}
if configuration.Password.Iterations < 1 {
validator.Push(fmt.Errorf("The number of iterations specified is invalid, must be 1 or more, you configured %d", configuration.Password.Iterations))
if config.Password.Iterations < 1 {
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidIterations, config.Password.Iterations))
}
}
}
func validateFileAuthenticationBackendSHA512(configuration *schema.FileAuthenticationBackendConfiguration) {
func validateFileAuthenticationBackendSHA512(config *schema.FileAuthenticationBackendConfiguration) {
// Iterations (time).
if configuration.Password.Iterations == 0 {
configuration.Password.Iterations = schema.DefaultPasswordSHA512Configuration.Iterations
if config.Password.Iterations == 0 {
config.Password.Iterations = schema.DefaultPasswordSHA512Configuration.Iterations
}
}
func validateFileAuthenticationBackendArgon2id(configuration *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
func validateFileAuthenticationBackendArgon2id(config *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
// Iterations (time).
if configuration.Password.Iterations == 0 {
configuration.Password.Iterations = schema.DefaultPasswordConfiguration.Iterations
if config.Password.Iterations == 0 {
config.Password.Iterations = schema.DefaultPasswordConfiguration.Iterations
}
// Parallelism.
if configuration.Password.Parallelism == 0 {
configuration.Password.Parallelism = schema.DefaultPasswordConfiguration.Parallelism
} else if configuration.Password.Parallelism < 1 {
validator.Push(fmt.Errorf("Parallelism for argon2id must be 1 or more, you configured %d", configuration.Password.Parallelism))
if config.Password.Parallelism == 0 {
config.Password.Parallelism = schema.DefaultPasswordConfiguration.Parallelism
} else if config.Password.Parallelism < 1 {
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidParallelism, config.Password.Parallelism))
}
// Memory.
if configuration.Password.Memory == 0 {
configuration.Password.Memory = schema.DefaultPasswordConfiguration.Memory
} else if configuration.Password.Memory < configuration.Password.Parallelism*8 {
validator.Push(fmt.Errorf("Memory for argon2id must be %d or more (parallelism * 8), you configured memory as %d and parallelism as %d", configuration.Password.Parallelism*8, configuration.Password.Memory, configuration.Password.Parallelism))
if config.Password.Memory == 0 {
config.Password.Memory = schema.DefaultPasswordConfiguration.Memory
} else if config.Password.Memory < config.Password.Parallelism*8 {
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidMemory, config.Password.Parallelism, config.Password.Parallelism*8, config.Password.Memory))
}
// Key Length.
if configuration.Password.KeyLength == 0 {
configuration.Password.KeyLength = schema.DefaultPasswordConfiguration.KeyLength
} else if configuration.Password.KeyLength < 16 {
validator.Push(fmt.Errorf("Key length for argon2id must be 16, you configured %d", configuration.Password.KeyLength))
if config.Password.KeyLength == 0 {
config.Password.KeyLength = schema.DefaultPasswordConfiguration.KeyLength
} else if config.Password.KeyLength < 16 {
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidKeyLength, config.Password.KeyLength))
}
}
func validateLDAPAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
if configuration.Timeout == 0 {
configuration.Timeout = schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout
func validateLDAPAuthenticationBackend(config *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
if config.Timeout == 0 {
config.Timeout = schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout
}
if configuration.Implementation == "" {
configuration.Implementation = schema.DefaultLDAPAuthenticationBackendConfiguration.Implementation
if config.Implementation == "" {
config.Implementation = schema.DefaultLDAPAuthenticationBackendConfiguration.Implementation
}
if configuration.TLS == nil {
configuration.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
if config.TLS == nil {
config.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
}
if configuration.TLS.MinimumVersion == "" {
configuration.TLS.MinimumVersion = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion
if config.TLS.MinimumVersion == "" {
config.TLS.MinimumVersion = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion
}
if _, err := utils.TLSStringToTLSConfigVersion(configuration.TLS.MinimumVersion); err != nil {
validator.Push(fmt.Errorf("error occurred validating the LDAP minimum_tls_version key with value %s: %v", configuration.TLS.MinimumVersion, err))
if _, err := utils.TLSStringToTLSConfigVersion(config.TLS.MinimumVersion); err != nil {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendTLSMinVersion, config.TLS.MinimumVersion, err))
}
switch configuration.Implementation {
switch config.Implementation {
case schema.LDAPImplementationCustom:
setDefaultImplementationCustomLDAPAuthenticationBackend(configuration)
setDefaultImplementationCustomLDAPAuthenticationBackend(config)
case schema.LDAPImplementationActiveDirectory:
setDefaultImplementationActiveDirectoryLDAPAuthenticationBackend(configuration)
setDefaultImplementationActiveDirectoryLDAPAuthenticationBackend(config)
default:
validator.Push(fmt.Errorf("authentication backend ldap implementation must be blank or one of the following values `%s`, `%s`", schema.LDAPImplementationCustom, schema.LDAPImplementationActiveDirectory))
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, config.Implementation, strings.Join([]string{schema.LDAPImplementationCustom, schema.LDAPImplementationActiveDirectory}, "', '")))
}
if strings.Contains(configuration.UsersFilter, "{0}") {
validator.Push(fmt.Errorf("authentication backend ldap users filter must not contain removed placeholders" +
", {0} has been replaced with {input}"))
if strings.Contains(config.UsersFilter, "{0}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "users_filter", "{0}", "{input}"))
}
if strings.Contains(configuration.GroupsFilter, "{0}") ||
strings.Contains(configuration.GroupsFilter, "{1}") {
validator.Push(fmt.Errorf("authentication backend ldap groups filter must not contain removed " +
"placeholders, {0} has been replaced with {input} and {1} has been replaced with {username}"))
if strings.Contains(config.GroupsFilter, "{0}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{0}", "{input}"))
}
if configuration.URL == "" {
validator.Push(errors.New("Please provide a URL to the LDAP server"))
if strings.Contains(config.GroupsFilter, "{1}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{1}", "{username}"))
}
if config.URL == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "url"))
} else {
ldapURL, serverName := validateLDAPURL(configuration.URL, validator)
configuration.URL = ldapURL
if configuration.TLS.ServerName == "" {
configuration.TLS.ServerName = serverName
}
validateLDAPAuthenticationBackendURL(config, validator)
}
validateLDAPRequiredParameters(configuration, validator)
validateLDAPRequiredParameters(config, validator)
}
// Wrapper for test purposes to exclude the hostname from the return.
func validateLDAPURLSimple(ldapURL string, validator *schema.StructValidator) (finalURL string) {
finalURL, _ = validateLDAPURL(ldapURL, validator)
func validateLDAPAuthenticationBackendURL(config *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
var (
parsedURL *url.URL
err error
)
return finalURL
}
if parsedURL, err = url.Parse(config.URL); err != nil {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendURLNotParsable, err))
func validateLDAPURL(ldapURL string, validator *schema.StructValidator) (finalURL string, hostname string) {
parsedURL, err := url.Parse(ldapURL)
if err != nil {
validator.Push(errors.New("Unable to parse URL to ldap server. The scheme is probably missing: ldap:// or ldaps://"))
return "", ""
return
}
if !(parsedURL.Scheme == schemeLDAP || parsedURL.Scheme == schemeLDAPS) {
validator.Push(errors.New("Unknown scheme for ldap url, should be ldap:// or ldaps://"))
return "", ""
if parsedURL.Scheme != schemeLDAP && parsedURL.Scheme != schemeLDAPS {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendURLInvalidScheme, parsedURL.Scheme))
return
}
return parsedURL.String(), parsedURL.Hostname()
config.URL = parsedURL.String()
if config.TLS.ServerName == "" {
config.TLS.ServerName = parsedURL.Hostname()
}
}
func validateLDAPRequiredParameters(configuration *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
func validateLDAPRequiredParameters(config *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
// TODO: see if it's possible to disable this check if disable_reset_password is set and when anonymous/user binding is supported (#101 and #387).
if configuration.User == "" {
validator.Push(errors.New("Please provide a user name to connect to the LDAP server"))
if config.User == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "user"))
}
// TODO: see if it's possible to disable this check if disable_reset_password is set and when anonymous/user binding is supported (#101 and #387).
if configuration.Password == "" {
validator.Push(errors.New("Please provide a password to connect to the LDAP server"))
if config.Password == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "password"))
}
if configuration.BaseDN == "" {
validator.Push(errors.New("Please provide a base DN to connect to the LDAP server"))
if config.BaseDN == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "base_dn"))
}
if configuration.UsersFilter == "" {
validator.Push(errors.New("Please provide a users filter with `users_filter` attribute"))
if config.UsersFilter == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "users_filter"))
} else {
if !strings.HasPrefix(configuration.UsersFilter, "(") || !strings.HasSuffix(configuration.UsersFilter, ")") {
validator.Push(errors.New("The users filter should contain enclosing parenthesis. For instance {username_attribute}={input} should be ({username_attribute}={input})"))
if !strings.HasPrefix(config.UsersFilter, "(") || !strings.HasSuffix(config.UsersFilter, ")") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "users_filter", config.UsersFilter, config.UsersFilter))
}
if !strings.Contains(configuration.UsersFilter, "{username_attribute}") {
validator.Push(errors.New("Unable to detect {username_attribute} placeholder in users_filter, your configuration is broken. " +
"Please review configuration options listed at https://www.authelia.com/docs/configuration/authentication/ldap.html"))
if !strings.Contains(config.UsersFilter, "{username_attribute}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, "users_filter", "username_attribute"))
}
// This test helps the user know that users_filter is broken after the breaking change induced by this commit.
if !strings.Contains(configuration.UsersFilter, "{0}") && !strings.Contains(configuration.UsersFilter, "{input}") {
validator.Push(errors.New("Unable to detect {input} placeholder in users_filter, your configuration might be broken. " +
"Please review configuration options listed at https://www.authelia.com/docs/configuration/authentication/ldap.html"))
if !strings.Contains(config.UsersFilter, "{input}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, "users_filter", "input"))
}
}
if configuration.GroupsFilter == "" {
validator.Push(errors.New("Please provide a groups filter with `groups_filter` attribute"))
} else if !strings.HasPrefix(configuration.GroupsFilter, "(") || !strings.HasSuffix(configuration.GroupsFilter, ")") {
validator.Push(errors.New("The groups filter should contain enclosing parenthesis. For instance cn={input} should be (cn={input})"))
if config.GroupsFilter == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "groups_filter"))
} else if !strings.HasPrefix(config.GroupsFilter, "(") || !strings.HasSuffix(config.GroupsFilter, ")") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "groups_filter", config.GroupsFilter, config.GroupsFilter))
}
}
func setDefaultImplementationActiveDirectoryLDAPAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration) {
if configuration.UsersFilter == "" {
configuration.UsersFilter = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter
func setDefaultImplementationActiveDirectoryLDAPAuthenticationBackend(config *schema.LDAPAuthenticationBackendConfiguration) {
if config.UsersFilter == "" {
config.UsersFilter = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter
}
if configuration.UsernameAttribute == "" {
configuration.UsernameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute
if config.UsernameAttribute == "" {
config.UsernameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute
}
if configuration.DisplayNameAttribute == "" {
configuration.DisplayNameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute
if config.DisplayNameAttribute == "" {
config.DisplayNameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute
}
if configuration.MailAttribute == "" {
configuration.MailAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute
if config.MailAttribute == "" {
config.MailAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute
}
if configuration.GroupsFilter == "" {
configuration.GroupsFilter = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter
if config.GroupsFilter == "" {
config.GroupsFilter = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter
}
if configuration.GroupNameAttribute == "" {
configuration.GroupNameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute
if config.GroupNameAttribute == "" {
config.GroupNameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute
}
}
func setDefaultImplementationCustomLDAPAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration) {
if configuration.UsernameAttribute == "" {
configuration.UsernameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.UsernameAttribute
func setDefaultImplementationCustomLDAPAuthenticationBackend(config *schema.LDAPAuthenticationBackendConfiguration) {
if config.UsernameAttribute == "" {
config.UsernameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.UsernameAttribute
}
if configuration.GroupNameAttribute == "" {
configuration.GroupNameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.GroupNameAttribute
if config.GroupNameAttribute == "" {
config.GroupNameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.GroupNameAttribute
}
if configuration.MailAttribute == "" {
configuration.MailAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.MailAttribute
if config.MailAttribute == "" {
config.MailAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.MailAttribute
}
if configuration.DisplayNameAttribute == "" {
configuration.DisplayNameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.DisplayNameAttribute
if config.DisplayNameAttribute == "" {
config.DisplayNameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.DisplayNameAttribute
}
}

View File

@ -23,7 +23,7 @@ func TestShouldRaiseErrorWhenBothBackendsProvided(t *testing.T) {
ValidateAuthenticationBackend(&backendConfig, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "You cannot provide both `ldap` and `file` objects in `authentication_backend`")
assert.EqualError(t, validator.Errors()[0], "authentication_backend: please ensure only one of the 'file' or 'ldap' backend is configured")
}
func TestShouldRaiseErrorWhenNoBackendProvided(t *testing.T) {
@ -33,19 +33,19 @@ func TestShouldRaiseErrorWhenNoBackendProvided(t *testing.T) {
ValidateAuthenticationBackend(&backendConfig, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "Please provide `ldap` or `file` object in `authentication_backend`")
assert.EqualError(t, validator.Errors()[0], "authentication_backend: you must ensure either the 'file' or 'ldap' authentication backend is configured")
}
type FileBasedAuthenticationBackend struct {
suite.Suite
configuration schema.AuthenticationBackendConfiguration
validator *schema.StructValidator
config schema.AuthenticationBackendConfiguration
validator *schema.StructValidator
}
func (suite *FileBasedAuthenticationBackend) SetupTest() {
suite.validator = schema.NewStructValidator()
suite.configuration = schema.AuthenticationBackendConfiguration{}
suite.configuration.File = &schema.FileAuthenticationBackendConfiguration{Path: "/a/path", Password: &schema.PasswordConfiguration{
suite.config = schema.AuthenticationBackendConfiguration{}
suite.config.File = &schema.FileAuthenticationBackendConfiguration{Path: "/a/path", Password: &schema.PasswordConfiguration{
Algorithm: schema.DefaultPasswordConfiguration.Algorithm,
Iterations: schema.DefaultPasswordConfiguration.Iterations,
Parallelism: schema.DefaultPasswordConfiguration.Parallelism,
@ -53,150 +53,150 @@ func (suite *FileBasedAuthenticationBackend) SetupTest() {
KeyLength: schema.DefaultPasswordConfiguration.KeyLength,
SaltLength: schema.DefaultPasswordConfiguration.SaltLength,
}}
suite.configuration.File.Password.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
suite.config.File.Password.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
}
func (suite *FileBasedAuthenticationBackend) TestShouldValidateCompleteConfiguration() {
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenNoPathProvided() {
suite.configuration.File.Path = ""
suite.config.File.Path = ""
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a `path` for the users database in `authentication_backend`")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: option 'path' is required")
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenMemoryNotMoreThanEightTimesParallelism() {
suite.configuration.File.Password.Memory = 8
suite.configuration.File.Password.Parallelism = 2
suite.config.File.Password.Memory = 8
suite.config.File.Password.Parallelism = 2
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Memory for argon2id must be 16 or more (parallelism * 8), you configured memory as 8 and parallelism as 2")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'memory' must at least be parallelism multiplied by 8 when using algorithm 'argon2id' with parallelism 2 it should be at least 16 but it is configured as '8'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultConfigurationWhenBlank() {
suite.configuration.File.Password = &schema.PasswordConfiguration{}
suite.config.File.Password = &schema.PasswordConfiguration{}
suite.Assert().Equal(0, suite.configuration.File.Password.KeyLength)
suite.Assert().Equal(0, suite.configuration.File.Password.Iterations)
suite.Assert().Equal(0, suite.configuration.File.Password.SaltLength)
suite.Assert().Equal("", suite.configuration.File.Password.Algorithm)
suite.Assert().Equal(0, suite.configuration.File.Password.Memory)
suite.Assert().Equal(0, suite.configuration.File.Password.Parallelism)
suite.Assert().Equal(0, suite.config.File.Password.KeyLength)
suite.Assert().Equal(0, suite.config.File.Password.Iterations)
suite.Assert().Equal(0, suite.config.File.Password.SaltLength)
suite.Assert().Equal("", suite.config.File.Password.Algorithm)
suite.Assert().Equal(0, suite.config.File.Password.Memory)
suite.Assert().Equal(0, suite.config.File.Password.Parallelism)
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().Len(suite.validator.Errors(), 0)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.KeyLength, suite.configuration.File.Password.KeyLength)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.configuration.File.Password.Iterations)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.SaltLength, suite.configuration.File.Password.SaltLength)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.configuration.File.Password.Algorithm)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Memory, suite.configuration.File.Password.Memory)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Parallelism, suite.configuration.File.Password.Parallelism)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.KeyLength, suite.config.File.Password.KeyLength)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.config.File.Password.Iterations)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.SaltLength, suite.config.File.Password.SaltLength)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.config.File.Password.Algorithm)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Memory, suite.config.File.Password.Memory)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Parallelism, suite.config.File.Password.Parallelism)
}
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultConfigurationWhenOnlySHA512Set() {
suite.configuration.File.Password = &schema.PasswordConfiguration{}
suite.Assert().Equal("", suite.configuration.File.Password.Algorithm)
suite.configuration.File.Password.Algorithm = "sha512"
suite.config.File.Password = &schema.PasswordConfiguration{}
suite.Assert().Equal("", suite.config.File.Password.Algorithm)
suite.config.File.Password.Algorithm = "sha512"
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().Len(suite.validator.Errors(), 0)
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.KeyLength, suite.configuration.File.Password.KeyLength)
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Iterations, suite.configuration.File.Password.Iterations)
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.SaltLength, suite.configuration.File.Password.SaltLength)
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Algorithm, suite.configuration.File.Password.Algorithm)
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Memory, suite.configuration.File.Password.Memory)
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Parallelism, suite.configuration.File.Password.Parallelism)
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.KeyLength, suite.config.File.Password.KeyLength)
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Iterations, suite.config.File.Password.Iterations)
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.SaltLength, suite.config.File.Password.SaltLength)
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Algorithm, suite.config.File.Password.Algorithm)
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Memory, suite.config.File.Password.Memory)
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Parallelism, suite.config.File.Password.Parallelism)
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenKeyLengthTooLow() {
suite.configuration.File.Password.KeyLength = 1
suite.config.File.Password.KeyLength = 1
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Key length for argon2id must be 16, you configured 1")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'key_length' must be 16 or more when using algorithm 'argon2id' but it is configured as '1'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSaltLengthTooLow() {
suite.configuration.File.Password.SaltLength = -1
suite.config.File.Password.SaltLength = -1
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "The salt length must be 2 or more, you configured -1")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'salt_length' must be 2 or more but it is configured a '-1'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBadAlgorithmDefined() {
suite.configuration.File.Password.Algorithm = "bogus"
suite.config.File.Password.Algorithm = "bogus"
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Unknown hashing algorithm supplied, valid values are argon2id and sha512, you configured 'bogus'")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'algorithm' must be either 'argon2id' or 'sha512' but it is configured as 'bogus'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenIterationsTooLow() {
suite.configuration.File.Password.Iterations = -1
suite.config.File.Password.Iterations = -1
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "The number of iterations specified is invalid, must be 1 or more, you configured -1")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'iterations' must be 1 or more but it is configured as '-1'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenParallelismTooLow() {
suite.configuration.File.Password.Parallelism = -1
suite.config.File.Password.Parallelism = -1
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Parallelism for argon2id must be 1 or more, you configured -1")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'parallelism' must be 1 or more when using algorithm 'argon2id' but it is configured as '-1'")
}
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultValues() {
suite.configuration.File.Password.Algorithm = ""
suite.configuration.File.Password.Iterations = 0
suite.configuration.File.Password.SaltLength = 0
suite.configuration.File.Password.Memory = 0
suite.configuration.File.Password.Parallelism = 0
suite.config.File.Password.Algorithm = ""
suite.config.File.Password.Iterations = 0
suite.config.File.Password.SaltLength = 0
suite.config.File.Password.Memory = 0
suite.config.File.Password.Parallelism = 0
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.configuration.File.Password.Algorithm)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.configuration.File.Password.Iterations)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.SaltLength, suite.configuration.File.Password.SaltLength)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Memory, suite.configuration.File.Password.Memory)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Parallelism, suite.configuration.File.Password.Parallelism)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.config.File.Password.Algorithm)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.config.File.Password.Iterations)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.SaltLength, suite.config.File.Password.SaltLength)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Memory, suite.config.File.Password.Memory)
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Parallelism, suite.config.File.Password.Parallelism)
}
func TestFileBasedAuthenticationBackend(t *testing.T) {
@ -205,289 +205,265 @@ func TestFileBasedAuthenticationBackend(t *testing.T) {
type LDAPAuthenticationBackendSuite struct {
suite.Suite
configuration schema.AuthenticationBackendConfiguration
validator *schema.StructValidator
config schema.AuthenticationBackendConfiguration
validator *schema.StructValidator
}
func (suite *LDAPAuthenticationBackendSuite) SetupTest() {
suite.validator = schema.NewStructValidator()
suite.configuration = schema.AuthenticationBackendConfiguration{}
suite.configuration.LDAP = &schema.LDAPAuthenticationBackendConfiguration{}
suite.configuration.LDAP.Implementation = schema.LDAPImplementationCustom
suite.configuration.LDAP.URL = testLDAPURL
suite.configuration.LDAP.User = testLDAPUser
suite.configuration.LDAP.Password = testLDAPPassword
suite.configuration.LDAP.BaseDN = testLDAPBaseDN
suite.configuration.LDAP.UsernameAttribute = "uid"
suite.configuration.LDAP.UsersFilter = "({username_attribute}={input})"
suite.configuration.LDAP.GroupsFilter = "(cn={input})"
suite.config = schema.AuthenticationBackendConfiguration{}
suite.config.LDAP = &schema.LDAPAuthenticationBackendConfiguration{}
suite.config.LDAP.Implementation = schema.LDAPImplementationCustom
suite.config.LDAP.URL = testLDAPURL
suite.config.LDAP.User = testLDAPUser
suite.config.LDAP.Password = testLDAPPassword
suite.config.LDAP.BaseDN = testLDAPBaseDN
suite.config.LDAP.UsernameAttribute = "uid"
suite.config.LDAP.UsersFilter = "({username_attribute}={input})"
suite.config.LDAP.GroupsFilter = "(cn={input})"
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateCompleteConfiguration() {
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() {
suite.configuration.LDAP.Implementation = ""
suite.configuration.LDAP.UsernameAttribute = ""
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
suite.config.LDAP.Implementation = ""
suite.config.LDAP.UsernameAttribute = ""
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().Equal(schema.LDAPImplementationCustom, suite.configuration.LDAP.Implementation)
suite.Assert().Equal(schema.LDAPImplementationCustom, suite.config.LDAP.Implementation)
suite.Assert().Equal(suite.configuration.LDAP.UsernameAttribute, schema.DefaultLDAPAuthenticationBackendConfiguration.UsernameAttribute)
suite.Assert().Equal(suite.config.LDAP.UsernameAttribute, schema.DefaultLDAPAuthenticationBackendConfiguration.UsernameAttribute)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementationIsInvalidMSAD() {
suite.configuration.LDAP.Implementation = "masd"
suite.config.LDAP.Implementation = "masd"
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication backend ldap implementation must be blank or one of the following values `custom`, `activedirectory`")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'implementation' is configured as 'masd' but must be one of the following values: 'custom', 'activedirectory'")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenURLNotProvided() {
suite.configuration.LDAP.URL = ""
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
suite.config.LDAP.URL = ""
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a URL to the LDAP server")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'url' is required")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenUserNotProvided() {
suite.configuration.LDAP.User = ""
suite.config.LDAP.User = ""
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a user name to connect to the LDAP server")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'user' is required")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenPasswordNotProvided() {
suite.configuration.LDAP.Password = ""
suite.config.LDAP.Password = ""
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a password to connect to the LDAP server")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'password' is required")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenBaseDNNotProvided() {
suite.configuration.LDAP.BaseDN = ""
suite.config.LDAP.BaseDN = ""
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a base DN to connect to the LDAP server")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'base_dn' is required")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseOnEmptyGroupsFilter() {
suite.configuration.LDAP.GroupsFilter = ""
suite.config.LDAP.GroupsFilter = ""
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a groups filter with `groups_filter` attribute")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'groups_filter' is required")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseOnEmptyUsersFilter() {
suite.configuration.LDAP.UsersFilter = ""
suite.config.LDAP.UsersFilter = ""
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Please provide a users filter with `users_filter` attribute")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' is required")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldNotRaiseOnEmptyUsernameAttribute() {
suite.configuration.LDAP.UsernameAttribute = ""
suite.config.LDAP.UsernameAttribute = ""
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseOnBadRefreshInterval() {
suite.configuration.RefreshInterval = "blah"
suite.config.RefreshInterval = "blah"
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Auth Backend `refresh_interval` is configured to 'blah' but it must be either a duration notation or one of 'disable', or 'always'. Error from parser: could not convert the input string of blah into a duration")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: option 'refresh_interval' is configured to 'blah' but it must be either a duration notation or one of 'disable', or 'always': could not parse 'blah' as a duration")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultImplementation() {
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.Assert().Equal(schema.LDAPImplementationCustom, suite.configuration.LDAP.Implementation)
suite.Assert().Equal(schema.LDAPImplementationCustom, suite.config.LDAP.Implementation)
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorOnBadFilterPlaceholders() {
suite.configuration.LDAP.UsersFilter = "(&({username_attribute}={0})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
suite.configuration.LDAP.GroupsFilter = "(&(member={0})(objectClass=group)(objectCategory=group))"
suite.config.LDAP.UsersFilter = "(&({username_attribute}={0})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
suite.config.LDAP.GroupsFilter = "(&({username_attribute}={1})(member={0})(objectClass=group)(objectCategory=group))"
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().True(suite.validator.HasErrors())
suite.Require().Len(suite.validator.Errors(), 2)
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication backend ldap users filter must "+
"not contain removed placeholders, {0} has been replaced with {input}")
suite.Assert().EqualError(suite.validator.Errors()[1], "authentication backend ldap groups filter must "+
"not contain removed placeholders, "+
"{0} has been replaced with {input} and {1} has been replaced with {username}")
suite.Require().Len(suite.validator.Errors(), 4)
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' has an invalid placeholder: '{0}' has been removed, please use '{input}' instead")
suite.Assert().EqualError(suite.validator.Errors()[1], "authentication_backend: ldap: option 'groups_filter' has an invalid placeholder: '{0}' has been removed, please use '{input}' instead")
suite.Assert().EqualError(suite.validator.Errors()[2], "authentication_backend: ldap: option 'groups_filter' has an invalid placeholder: '{1}' has been removed, please use '{username}' instead")
suite.Assert().EqualError(suite.validator.Errors()[3], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it is required")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttribute() {
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.Assert().Equal("cn", suite.configuration.LDAP.GroupNameAttribute)
suite.Assert().Equal("cn", suite.config.LDAP.GroupNameAttribute)
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute() {
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.Assert().Equal("mail", suite.configuration.LDAP.MailAttribute)
suite.Assert().Equal("mail", suite.config.LDAP.MailAttribute)
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttribute() {
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.Assert().Equal("displayName", suite.configuration.LDAP.DisplayNameAttribute)
suite.Assert().Equal("displayName", suite.config.LDAP.DisplayNameAttribute)
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultRefreshInterval() {
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.Assert().Equal("5m", suite.configuration.RefreshInterval)
suite.Assert().Equal("5m", suite.config.RefreshInterval)
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesNotContainEnclosingParenthesis() {
suite.configuration.LDAP.UsersFilter = "{username_attribute}={input}"
suite.config.LDAP.UsersFilter = "{username_attribute}={input}"
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "The users filter should contain enclosing parenthesis. For instance {username_attribute}={input} should be ({username_attribute}={input})")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain enclosing parenthesis: '{username_attribute}={input}' should probably be '({username_attribute}={input})'")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseWhenGroupsFilterDoesNotContainEnclosingParenthesis() {
suite.configuration.LDAP.GroupsFilter = "cn={input}"
suite.config.LDAP.GroupsFilter = "cn={input}"
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "The groups filter should contain enclosing parenthesis. For instance cn={input} should be (cn={input})")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'groups_filter' must contain enclosing parenthesis: 'cn={input}' should probably be '(cn={input})'")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesNotContainUsernameAttribute() {
suite.configuration.LDAP.UsersFilter = "(&({mail_attribute}={input})(objectClass=person))"
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
suite.config.LDAP.UsersFilter = "(&({mail_attribute}={input})(objectClass=person))"
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Unable to detect {username_attribute} placeholder in users_filter, your configuration is broken. Please review configuration options listed at https://www.authelia.com/docs/configuration/authentication/ldap.html")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{username_attribute}' but it is required")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlaceholder() {
suite.configuration.LDAP.UsersFilter = "(&({username_attribute}={mail_attribute})(objectClass=person))"
suite.config.LDAP.UsersFilter = "(&({username_attribute}={mail_attribute})(objectClass=person))"
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Unable to detect {input} placeholder in users_filter, your configuration might be broken. Please review configuration options listed at https://www.authelia.com/docs/configuration/authentication/ldap.html")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldAdaptLDAPURL() {
suite.Assert().Equal("", validateLDAPURLSimple(loopback, suite.validator))
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Unknown scheme for ldap url, should be ldap:// or ldaps://")
suite.Assert().Equal("", validateLDAPURLSimple("127.0.0.1:636", suite.validator))
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 2)
suite.Assert().EqualError(suite.validator.Errors()[1], "Unable to parse URL to ldap server. The scheme is probably missing: ldap:// or ldaps://")
suite.Assert().Equal("ldap://127.0.0.1", validateLDAPURLSimple("ldap://127.0.0.1", suite.validator))
suite.Assert().Equal("ldap://127.0.0.1:390", validateLDAPURLSimple("ldap://127.0.0.1:390", suite.validator))
suite.Assert().Equal("ldap://127.0.0.1/abc", validateLDAPURLSimple("ldap://127.0.0.1/abc", suite.validator))
suite.Assert().Equal("ldap://127.0.0.1/abc?test=abc&x=y", validateLDAPURLSimple("ldap://127.0.0.1/abc?test=abc&x=y", suite.validator))
suite.Assert().Equal("ldaps://127.0.0.1:390", validateLDAPURLSimple("ldaps://127.0.0.1:390", suite.validator))
suite.Assert().Equal("ldaps://127.0.0.1", validateLDAPURLSimple("ldaps://127.0.0.1", suite.validator))
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it is required")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultTLSMinimumVersion() {
suite.configuration.LDAP.TLS = &schema.TLSConfig{MinimumVersion: ""}
suite.config.LDAP.TLS = &schema.TLSConfig{MinimumVersion: ""}
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.Assert().Equal(schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion, suite.configuration.LDAP.TLS.MinimumVersion)
suite.Assert().Equal(schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion, suite.config.LDAP.TLS.MinimumVersion)
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowInvalidTLSValue() {
suite.configuration.LDAP.TLS = &schema.TLSConfig{
suite.config.LDAP.TLS = &schema.TLSConfig{
MinimumVersion: "SSL2.0",
}
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "error occurred validating the LDAP minimum_tls_version key with value SSL2.0: supplied TLS version isn't supported")
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: tls: option 'minimum_tls_version' is invalid: SSL2.0: supplied tls version isn't supported")
}
func TestLdapAuthenticationBackend(t *testing.T) {
@ -496,83 +472,101 @@ func TestLdapAuthenticationBackend(t *testing.T) {
type ActiveDirectoryAuthenticationBackendSuite struct {
suite.Suite
configuration schema.AuthenticationBackendConfiguration
validator *schema.StructValidator
config schema.AuthenticationBackendConfiguration
validator *schema.StructValidator
}
func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() {
suite.validator = schema.NewStructValidator()
suite.configuration = schema.AuthenticationBackendConfiguration{}
suite.configuration.LDAP = &schema.LDAPAuthenticationBackendConfiguration{}
suite.configuration.LDAP.Implementation = schema.LDAPImplementationActiveDirectory
suite.configuration.LDAP.URL = testLDAPURL
suite.configuration.LDAP.User = testLDAPUser
suite.configuration.LDAP.Password = testLDAPPassword
suite.configuration.LDAP.BaseDN = testLDAPBaseDN
suite.configuration.LDAP.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
suite.config = schema.AuthenticationBackendConfiguration{}
suite.config.LDAP = &schema.LDAPAuthenticationBackendConfiguration{}
suite.config.LDAP.Implementation = schema.LDAPImplementationActiveDirectory
suite.config.LDAP.URL = testLDAPURL
suite.config.LDAP.User = testLDAPUser
suite.config.LDAP.Password = testLDAPPassword
suite.config.LDAP.BaseDN = testLDAPBaseDN
suite.config.LDAP.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
}
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirectoryDefaults() {
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout,
suite.configuration.LDAP.Timeout)
suite.config.LDAP.Timeout)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter,
suite.configuration.LDAP.UsersFilter)
suite.config.LDAP.UsersFilter)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute,
suite.configuration.LDAP.UsernameAttribute)
suite.config.LDAP.UsernameAttribute)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute,
suite.configuration.LDAP.DisplayNameAttribute)
suite.config.LDAP.DisplayNameAttribute)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute,
suite.configuration.LDAP.MailAttribute)
suite.config.LDAP.MailAttribute)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter,
suite.configuration.LDAP.GroupsFilter)
suite.config.LDAP.GroupsFilter)
suite.Assert().Equal(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute,
suite.configuration.LDAP.GroupNameAttribute)
suite.config.LDAP.GroupNameAttribute)
}
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.configuration.LDAP.Timeout = time.Second * 2
suite.configuration.LDAP.UsersFilter = "(&({username_attribute}={input})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
suite.configuration.LDAP.UsernameAttribute = "cn"
suite.configuration.LDAP.MailAttribute = "userPrincipalName"
suite.configuration.LDAP.DisplayNameAttribute = "name"
suite.configuration.LDAP.GroupsFilter = "(&(member={dn})(objectClass=group)(objectCategory=group))"
suite.configuration.LDAP.GroupNameAttribute = "distinguishedName"
suite.config.LDAP.Timeout = time.Second * 2
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
suite.config.LDAP.UsernameAttribute = "cn"
suite.config.LDAP.MailAttribute = "userPrincipalName"
suite.config.LDAP.DisplayNameAttribute = "name"
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=group)(objectCategory=group))"
suite.config.LDAP.GroupNameAttribute = "distinguishedName"
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout,
suite.configuration.LDAP.Timeout)
suite.config.LDAP.Timeout)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter,
suite.configuration.LDAP.UsersFilter)
suite.config.LDAP.UsersFilter)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute,
suite.configuration.LDAP.UsernameAttribute)
suite.config.LDAP.UsernameAttribute)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute,
suite.configuration.LDAP.DisplayNameAttribute)
suite.config.LDAP.DisplayNameAttribute)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute,
suite.configuration.LDAP.MailAttribute)
suite.config.LDAP.MailAttribute)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter,
suite.configuration.LDAP.GroupsFilter)
suite.config.LDAP.GroupsFilter)
suite.Assert().NotEqual(
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute,
suite.configuration.LDAP.GroupNameAttribute)
suite.config.LDAP.GroupNameAttribute)
}
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnInvalidURLWithHTTP() {
suite.config.LDAP.URL = "http://dc1:389"
validateLDAPAuthenticationBackendURL(suite.config.LDAP, suite.validator)
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'url' must have either the 'ldap' or 'ldaps' scheme but it is configured as 'http'")
}
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnInvalidURLWithBadCharacters() {
suite.config.LDAP.URL = "ldap://dc1:abc"
validateLDAPAuthenticationBackendURL(suite.config.LDAP, suite.validator)
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'url' could not be parsed: parse \"ldap://dc1:abc\": invalid port \":abc\" after host")
}
func TestActiveDirectoryAuthenticationBackend(t *testing.T) {

View File

@ -3,68 +3,59 @@ package validator
import (
"fmt"
"os"
"strings"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
)
// ValidateConfiguration and adapt the configuration read from file.
func ValidateConfiguration(configuration *schema.Configuration, validator *schema.StructValidator) {
if configuration.CertificatesDirectory != "" {
info, err := os.Stat(configuration.CertificatesDirectory)
if err != nil {
validator.Push(fmt.Errorf("Error checking certificate directory: %v", err))
func ValidateConfiguration(config *schema.Configuration, validator *schema.StructValidator) {
var err error
if config.CertificatesDirectory != "" {
var info os.FileInfo
if info, err = os.Stat(config.CertificatesDirectory); err != nil {
validator.Push(fmt.Errorf("the location 'certificates_directory' could not be inspected: %w", err))
} else if !info.IsDir() {
validator.Push(fmt.Errorf("The path %s specified for certificate_directory is not a directory", configuration.CertificatesDirectory))
validator.Push(fmt.Errorf("the location 'certificates_directory' refers to '%s' is not a directory", config.CertificatesDirectory))
}
}
if configuration.JWTSecret == "" {
validator.Push(fmt.Errorf("Provide a JWT secret using \"jwt_secret\" key"))
if config.JWTSecret == "" {
validator.Push(fmt.Errorf("option 'jwt_secret' is required"))
}
if configuration.DefaultRedirectionURL != "" {
err := utils.IsStringAbsURL(configuration.DefaultRedirectionURL)
if err != nil {
validator.Push(fmt.Errorf("Value for \"default_redirection_url\" is invalid: %+v", err))
if config.DefaultRedirectionURL != "" {
if err = utils.IsStringAbsURL(config.DefaultRedirectionURL); err != nil {
validator.Push(fmt.Errorf("option 'default_redirection_url' is invalid: %s", strings.ReplaceAll(err.Error(), "like 'http://' or 'https://'", "like 'ldap://' or 'ldaps://'")))
}
}
ValidateTheme(configuration, validator)
ValidateTheme(config, validator)
ValidateLogging(configuration, validator)
ValidateLog(config, validator)
ValidateTOTP(configuration, validator)
ValidateTOTP(config, validator)
ValidateAuthenticationBackend(&configuration.AuthenticationBackend, validator)
ValidateAuthenticationBackend(&config.AuthenticationBackend, validator)
ValidateAccessControl(&configuration.AccessControl, validator)
ValidateAccessControl(config, validator)
ValidateRules(configuration.AccessControl, validator)
ValidateRules(config, validator)
ValidateSession(&configuration.Session, validator)
ValidateSession(&config.Session, validator)
if configuration.Regulation == nil {
configuration.Regulation = &schema.DefaultRegulationConfiguration
}
ValidateRegulation(config, validator)
ValidateRegulation(configuration.Regulation, validator)
ValidateServer(config, validator)
ValidateServer(configuration, validator)
ValidateStorage(config.Storage, validator)
ValidateStorage(configuration.Storage, validator)
ValidateNotifier(config.Notifier, validator)
if configuration.Notifier == nil {
validator.Push(fmt.Errorf("A notifier configuration must be provided"))
} else {
ValidateNotifier(configuration.Notifier, validator)
}
ValidateIdentityProviders(&config.IdentityProviders, validator)
ValidateIdentityProviders(&configuration.IdentityProviders, validator)
if configuration.NTP == nil {
configuration.NTP = &schema.DefaultNTPConfiguration
}
ValidateNTP(configuration.NTP, validator)
ValidateNTP(config, validator)
}

View File

@ -52,7 +52,7 @@ func TestShouldEnsureNotifierConfigIsProvided(t *testing.T) {
ValidateConfiguration(&config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "A notifier configuration must be provided")
assert.EqualError(t, validator.Errors()[0], "notifier: you must ensure either the 'smtp' or 'filesystem' notifier is configured")
}
func TestShouldAddDefaultAccessControl(t *testing.T) {
@ -84,8 +84,8 @@ func TestShouldRaiseErrorWithUndefinedJWTSecretKey(t *testing.T) {
require.Len(t, validator.Errors(), 1)
require.Len(t, validator.Warnings(), 1)
assert.EqualError(t, validator.Errors()[0], "Provide a JWT secret using \"jwt_secret\" key")
assert.EqualError(t, validator.Warnings()[0], "No access control rules have been defined so the default policy two_factor will be applied to all requests")
assert.EqualError(t, validator.Errors()[0], "option 'jwt_secret' is required")
assert.EqualError(t, validator.Warnings()[0], "access control: no rules have been specified so the 'default_policy' of 'two_factor' is going to be applied to all requests")
}
func TestShouldRaiseErrorWithBadDefaultRedirectionURL(t *testing.T) {
@ -97,8 +97,8 @@ func TestShouldRaiseErrorWithBadDefaultRedirectionURL(t *testing.T) {
require.Len(t, validator.Errors(), 1)
require.Len(t, validator.Warnings(), 1)
assert.EqualError(t, validator.Errors()[0], "Value for \"default_redirection_url\" is invalid: the url 'bad_default_redirection_url' is not absolute because it doesn't start with a scheme like 'http://' or 'https://'")
assert.EqualError(t, validator.Warnings()[0], "No access control rules have been defined so the default policy two_factor will be applied to all requests")
assert.EqualError(t, validator.Errors()[0], "option 'default_redirection_url' is invalid: the url 'bad_default_redirection_url' is not absolute because it doesn't start with a scheme like 'ldap://' or 'ldaps://'")
assert.EqualError(t, validator.Warnings()[0], "access control: no rules have been specified so the 'default_policy' of 'two_factor' is going to be applied to all requests")
}
func TestShouldNotOverrideCertificatesDirectoryAndShouldPassWhenBlank(t *testing.T) {
@ -112,7 +112,7 @@ func TestShouldNotOverrideCertificatesDirectoryAndShouldPassWhenBlank(t *testing
require.Equal(t, "", config.CertificatesDirectory)
assert.EqualError(t, validator.Warnings()[0], "No access control rules have been defined so the default policy two_factor will be applied to all requests")
assert.EqualError(t, validator.Warnings()[0], "access control: no rules have been specified so the 'default_policy' of 'two_factor' is going to be applied to all requests")
}
func TestShouldRaiseErrorOnInvalidCertificatesDirectory(t *testing.T) {
@ -126,12 +126,12 @@ func TestShouldRaiseErrorOnInvalidCertificatesDirectory(t *testing.T) {
require.Len(t, validator.Warnings(), 1)
if runtime.GOOS == "windows" {
assert.EqualError(t, validator.Errors()[0], "Error checking certificate directory: CreateFile not-a-real-file.go: The system cannot find the file specified.")
assert.EqualError(t, validator.Errors()[0], "the location 'certificates_directory' could not be inspected: CreateFile not-a-real-file.go: The system cannot find the file specified.")
} else {
assert.EqualError(t, validator.Errors()[0], "Error checking certificate directory: stat not-a-real-file.go: no such file or directory")
assert.EqualError(t, validator.Errors()[0], "the location 'certificates_directory' could not be inspected: stat not-a-real-file.go: no such file or directory")
}
assert.EqualError(t, validator.Warnings()[0], "No access control rules have been defined so the default policy two_factor will be applied to all requests")
assert.EqualError(t, validator.Warnings()[0], "access control: no rules have been specified so the 'default_policy' of 'two_factor' is going to be applied to all requests")
validator = schema.NewStructValidator()
config.CertificatesDirectory = "const.go"
@ -141,8 +141,8 @@ func TestShouldRaiseErrorOnInvalidCertificatesDirectory(t *testing.T) {
require.Len(t, validator.Errors(), 1)
require.Len(t, validator.Warnings(), 1)
assert.EqualError(t, validator.Errors()[0], "The path const.go specified for certificate_directory is not a directory")
assert.EqualError(t, validator.Warnings()[0], "No access control rules have been defined so the default policy two_factor will be applied to all requests")
assert.EqualError(t, validator.Errors()[0], "the location 'certificates_directory' refers to 'const.go' is not a directory")
assert.EqualError(t, validator.Warnings()[0], "access control: no rules have been specified so the 'default_policy' of 'two_factor' is going to be applied to all requests")
}
func TestShouldNotRaiseErrorOnValidCertificatesDirectory(t *testing.T) {
@ -155,5 +155,5 @@ func TestShouldNotRaiseErrorOnValidCertificatesDirectory(t *testing.T) {
assert.Len(t, validator.Errors(), 0)
require.Len(t, validator.Warnings(), 1)
assert.EqualError(t, validator.Warnings()[0], "No access control rules have been defined so the default policy two_factor will be applied to all requests")
assert.EqualError(t, validator.Warnings()[0], "access control: no rules have been specified so the 'default_policy' of 'two_factor' is going to be applied to all requests")
}

View File

@ -1,6 +1,10 @@
package validator
import "regexp"
import (
"regexp"
"github.com/authelia/authelia/v4/internal/oidc"
)
const (
loopback = "127.0.0.1"
@ -46,64 +50,173 @@ const (
// Notifier Error constants.
const (
errFmtNotifierMultipleConfigured = "notifier: you can't configure more than one notifier, please ensure " +
"only 'smtp' or 'filesystem' is configured"
errFmtNotifierNotConfigured = "notifier: you must ensure either the 'smtp' or 'filesystem' notifier " +
errFmtNotifierMultipleConfigured = "notifier: please ensure only one of the 'smtp' or 'filesystem' notifier is configured"
errFmtNotifierNotConfigured = "notifier: you must ensure either the 'smtp' or 'filesystem' notifier " +
"is configured"
errFmtNotifierFileSystemFileNameNotConfigured = "filesystem notifier: the 'filename' must be configured"
errFmtNotifierSMTPNotConfigured = "smtp notifier: the '%s' must be configured"
errFmtNotifierFileSystemFileNameNotConfigured = "notifier: filesystem: option 'filename' is required "
errFmtNotifierSMTPNotConfigured = "notifier: smtp: option '%s' is required"
)
// Authentication Backend Error constants.
const (
errFmtAuthBackendNotConfigured = "authentication_backend: you must ensure either the 'file' or 'ldap' " +
"authentication backend is configured"
errFmtAuthBackendMultipleConfigured = "authentication_backend: please ensure only one of the 'file' or 'ldap' " +
"backend is configured"
errFmtAuthBackendRefreshInterval = "authentication_backend: option 'refresh_interval' is configured to '%s' but " +
"it must be either a duration notation or one of 'disable', or 'always': %w"
errFmtFileAuthBackendPathNotConfigured = "authentication_backend: file: option 'path' is required"
errFmtFileAuthBackendPasswordSaltLength = "authentication_backend: file: password: option 'salt_length' " +
"must be 2 or more but it is configured a '%d'"
errFmtFileAuthBackendPasswordUnknownAlg = "authentication_backend: file: password: option 'algorithm' " +
"must be either 'argon2id' or 'sha512' but it is configured as '%s'"
errFmtFileAuthBackendPasswordInvalidIterations = "authentication_backend: file: password: option " +
"'iterations' must be 1 or more but it is configured as '%d'"
errFmtFileAuthBackendPasswordArgon2idInvalidKeyLength = "authentication_backend: file: password: option " +
"'key_length' must be 16 or more when using algorithm 'argon2id' but it is configured as '%d'"
errFmtFileAuthBackendPasswordArgon2idInvalidParallelism = "authentication_backend: file: password: option " +
"'parallelism' must be 1 or more when using algorithm 'argon2id' but it is configured as '%d'"
errFmtFileAuthBackendPasswordArgon2idInvalidMemory = "authentication_backend: file: password: option 'memory' " +
"must at least be parallelism multiplied by 8 when using algorithm 'argon2id' " +
"with parallelism %d it should be at least %d but it is configured as '%d'"
errFmtLDAPAuthBackendMissingOption = "authentication_backend: ldap: option '%s' is required"
errFmtLDAPAuthBackendTLSMinVersion = "authentication_backend: ldap: tls: option " +
"'minimum_tls_version' is invalid: %s: %w"
errFmtLDAPAuthBackendImplementation = "authentication_backend: ldap: option 'implementation' " +
"is configured as '%s' but must be one of the following values: '%s'"
errFmtLDAPAuthBackendFilterReplacedPlaceholders = "authentication_backend: ldap: option " +
"'%s' has an invalid placeholder: '%s' has been removed, please use '%s' instead"
errFmtLDAPAuthBackendURLNotParsable = "authentication_backend: ldap: option " +
"'url' could not be parsed: %w"
errFmtLDAPAuthBackendURLInvalidScheme = "authentication_backend: ldap: option " +
"'url' must have either the 'ldap' or 'ldaps' scheme but it is configured as '%s'"
errFmtLDAPAuthBackendFilterEnclosingParenthesis = "authentication_backend: ldap: option " +
"'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'"
errFmtLDAPAuthBackendFilterMissingPlaceholder = "authentication_backend: ldap: option " +
"'%s' must contain the placeholder '{%s}' but it is required"
)
// TOTP Error constants.
const (
errFmtTOTPInvalidAlgorithm = "totp: algorithm '%s' is invalid: must be one of %s"
errFmtTOTPInvalidPeriod = "totp: period '%d' is invalid: must be 15 or more"
errFmtTOTPInvalidDigits = "totp: digits '%d' is invalid: must be 6 or 8"
errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of '%s' but it is configured as '%s'"
errFmtTOTPInvalidPeriod = "totp: option 'period' option must be 15 or more but it is configured as '%d'"
errFmtTOTPInvalidDigits = "totp: option 'digits' must be 6 or 8 but it is configured as '%d'"
)
// Storage Error constants.
const (
errStrStorage = "storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided"
errStrStorageEncryptionKeyMustBeProvided = "storage: 'encryption_key' configuration option must be provided"
errStrStorageEncryptionKeyTooShort = "storage: 'encryption_key' configuration option must be 20 characters or longer"
errFmtStorageUserPassMustBeProvided = "storage: %s: 'username' and 'password' configuration options must be provided" //nolint: gosec
errFmtStorageOptionMustBeProvided = "storage: %s: '%s' configuration option must be provided"
errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: 'mode' configuration option '%s' is invalid: must be one of '%s'"
errStrStorageEncryptionKeyMustBeProvided = "storage: option 'encryption_key' must is required"
errStrStorageEncryptionKeyTooShort = "storage: option 'encryption_key' must be 20 characters or longer"
errFmtStorageUserPassMustBeProvided = "storage: %s: option 'username' and 'password' are required" //nolint: gosec
errFmtStorageOptionMustBeProvided = "storage: %s: option '%s' is required"
errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: option 'mode' must be one of '%s' but it is configured as '%s'"
)
var storagePostgreSQLValidSSLModes = []string{testModeDisabled, "require", "verify-ca", "verify-full"}
// OpenID Error constants.
const (
errFmtOIDCClientsDuplicateID = "openid connect provider: one or more clients have the same ID"
errFmtOIDCClientsWithEmptyID = "openid connect provider: one or more clients have been configured with an empty ID"
errFmtOIDCNoClientsConfigured = "openid connect provider: no clients are configured"
errFmtOIDCNoPrivateKey = "openid connect provider: issuer private key must be provided"
errFmtOIDCClientInvalidSecret = "openid connect provider: client with ID '%s' has an empty secret"
errFmtOIDCClientPublicInvalidSecret = "openid connect provider: client with ID '%s' is public but does not have " +
"an empty secret"
errFmtOIDCClientRedirectURI = "openid connect provider: client with ID '%s' redirect URI %s has an " +
"invalid scheme %s, should be http or https"
errFmtOIDCClientRedirectURICantBeParsed = "openid connect provider: client with ID '%s' has an invalid redirect " +
"URI '%s' could not be parsed: %v"
errFmtOIDCClientRedirectURIPublic = "openid connect provider: client with ID '%s' redirect URI '%s' is " +
"only valid for the public client type, not the confidential client type"
errFmtOIDCClientRedirectURIAbsolute = "openid connect provider: client with ID '%s' redirect URI '%s' is invalid " +
"because it has no scheme when it should be http or https"
errFmtOIDCClientInvalidPolicy = "openid connect provider: client with ID '%s' has an invalid policy " +
"'%s', should be either 'one_factor' or 'two_factor'"
errFmtOIDCClientInvalidScope = "openid connect provider: client with ID '%s' has an invalid scope " +
"'%s', must be one of: '%s'"
errFmtOIDCClientInvalidGrantType = "openid connect provider: client with ID '%s' has an invalid grant type " +
"'%s', must be one of: '%s'"
errFmtOIDCClientInvalidResponseMode = "openid connect provider: client with ID '%s' has an invalid response mode " +
"'%s', must be one of: '%s'"
errFmtOIDCClientInvalidUserinfoAlgorithm = "openid connect provider: client with ID '%s' has an invalid userinfo signing " +
"algorithm '%s', must be one of: '%s'"
errFmtOIDCNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " +
"more clients configured"
errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' is required"
errFmtOIDCClientsDuplicateID = "identity_providers: oidc: one or more clients have the same id but all client" +
"id's must be unique"
errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: one or more clients have been configured with " +
"an empty id"
errFmtOIDCClientInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is required"
errFmtOIDCClientPublicInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is " +
"required to be empty when option 'public' is true"
errFmtOIDCClientRedirectURI = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
"invalid value: redirect uri '%s' must have a scheme of 'http' or 'https' but '%s' is configured"
errFmtOIDCClientRedirectURICantBeParsed = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
"invalid value: redirect uri '%s' could not be parsed: %v"
errFmtOIDCClientRedirectURIPublic = "identity_providers: oidc: client '%s': option 'redirect_uris' has the" +
"redirect uri '%s' when option 'public' is false but this is invalid as this uri is not valid " +
"for the openid connect confidential client type"
errFmtOIDCClientRedirectURIAbsolute = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
"invalid value: redirect uri '%s' must have the scheme 'http' or 'https' but it has no scheme"
errFmtOIDCClientInvalidPolicy = "identity_providers: oidc: client '%s': option 'policy' must be 'one_factor' " +
"or 'two_factor' but it is configured as '%s'"
errFmtOIDCClientInvalidEntry = "identity_providers: oidc: client '%s': option '%s' must only have the values " +
"'%s' but one option is configured as '%s'"
errFmtOIDCClientInvalidUserinfoAlgorithm = "identity_providers: oidc: client '%s': option " +
"'userinfo_signing_algorithm' must be one of '%s' but it is configured as '%s'"
errFmtOIDCServerInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " +
"configured to an unsafe value, it should be above 8 but it's configured to %d"
)
// Access Control error constants.
const (
errFmtAccessControlDefaultPolicyValue = "access control: option 'default_policy' must be one of '%s' but it is " +
"configured as '%s'"
errFmtAccessControlDefaultPolicyWithoutRules = "access control: 'default_policy' option '%s' is invalid: when " +
"no rules are specified it must be 'two_factor' or 'one_factor'"
errFmtAccessControlNetworkGroupIPCIDRInvalid = "access control: networks: network group '%s' is invalid: the " +
"network '%s' is not a valid IP or CIDR notation"
errFmtAccessControlWarnNoRulesDefaultPolicy = "access control: no rules have been specified so the " +
"'default_policy' of '%s' is going to be applied to all requests"
errFmtAccessControlRuleNoDomains = "access control: rule %s: rule is invalid: must have the option " +
"'domain' configured"
errFmtAccessControlRuleInvalidPolicy = "access control: rule %s: rule 'policy' option '%s' " +
"is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'"
errAccessControlRuleBypassPolicyInvalidWithSubjects = "access control: rule %s: 'policy' option 'bypass' is " +
"not supported when 'subject' option is configured: see " +
"https://www.authelia.com/docs/configuration/access-control.html#bypass"
errFmtAccessControlRuleNetworksInvalid = "access control: rule %s: the network '%s' is not a " +
"valid Group Name, IP, or CIDR notation"
errFmtAccessControlRuleResourceInvalid = "access control: rule %s: 'resources' option '%s' is " +
"invalid: %w"
errFmtAccessControlRuleSubjectInvalid = "access control: rule %s: 'subject' option '%s' is " +
"invalid: must start with 'user:' or 'group:'"
errFmtAccessControlRuleMethodInvalid = "access control: rule %s: 'methods' option '%s' is " +
"invalid: must be one of '%s'"
)
// Theme Error constants.
const (
errFmtThemeName = "option 'theme' must be one of '%s' but it is configured as '%s'"
)
// NTP Error constants.
const (
errFmtNTPVersion = "ntp: option 'version' must be either 3 or 4 but it is configured as '%d'"
errFmtNTPMaxDesync = "ntp: option 'max_desync' can't be parsed: %w"
)
// Session error constants.
const (
errFmtSessionCouldNotParseDuration = "session: option '%s' could not be parsed: %w"
errFmtSessionOptionRequired = "session: option '%s' is required"
errFmtSessionDomainMustBeRoot = "session: option 'domain' must be the domain you wish to protect not a wildcard domain but it is configured as '%s'"
errFmtSessionSameSite = "session: option 'same_site' must be one of '%s' but is configured as '%s'"
errFmtSessionSecretRequired = "session: option 'secret' is required when using the '%s' provider"
errFmtSessionRedisPortRange = "session: redis: option 'port' must be between 1 and 65535 but is configured as '%d'"
errFmtSessionRedisHostRequired = "session: redis: option 'host' is required"
errFmtSessionRedisHostOrNodesRequired = "session: redis: option 'host' or the 'high_availability' option 'nodes' is required"
errFmtSessionRedisSentinelMissingName = "session: redis: high_availability: option 'sentinel_name' is required"
errFmtSessionRedisSentinelNodeHostMissing = "session: redis: high_availability: option 'nodes': option 'host' is required for each node but one or more nodes are missing this"
)
// Regulation Error Consts.
const (
errFmtRegulationParseDuration = "regulation: option '%s' could not be parsed: %w"
errFmtRegulationFindTimeGreaterThanBanTime = "regulation: option 'find_time' must be less than or equal to option 'ban_time'"
)
// Server Error constants.
const (
errFmtServerTLSCert = "server: tls: option 'key' must also be accompanied by option 'certificate'"
errFmtServerTLSKey = "server: tls: option 'certificate' must also be accompanied by option 'key'"
errFmtServerPathNoForwardSlashes = "server: option 'path' must not contain any forward slashes"
errFmtServerPathAlphaNum = "server: option 'path' must only contain alpha numeric characters"
errFmtServerBufferSize = "server: option '%s_buffer_size' must be above 0 but it is configured as '%d'"
)
// Error constants.
const (
/*
@ -118,26 +231,25 @@ const (
errFmtReplacedConfigurationKey = "invalid configuration key '%s' was replaced by '%s'"
errFmtLoggingLevelInvalid = "the log level '%s' is invalid, must be one of: %s"
errFmtSessionSecretRedisProvider = "the session secret must be set when using the %s session provider"
errFmtSessionRedisPortRange = "the port must be between 1 and 65535 for the %s session provider"
errFmtSessionRedisHostRequired = "the host must be provided when using the %s session provider"
errFmtSessionRedisHostOrNodesRequired = "either the host or a node must be provided when using the %s session provider"
errFmtLoggingLevelInvalid = "log: option 'level' must be one of '%s' but it is configured as '%s'"
errFileHashing = "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password"
errFilePHashing = "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password"
errFilePOptions = "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password"
errAccessControlInvalidPolicyWithSubjects = "policy [bypass] for rule #%d domain %s with subjects %s is invalid. It is " +
"not supported to configure both policy bypass and subjects. For more information see: " +
"https://www.authelia.com/docs/configuration/access-control.html#combining-subjects-and-the-bypass-policy"
)
var validLoggingLevels = []string{"trace", "debug", "info", "warn", "error"}
var validHTTPRequestMethods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
var validStoragePostgreSQLSSLModes = []string{testModeDisabled, "require", "verify-ca", "verify-full"}
var validOIDCScopes = []string{"openid", "email", "profile", "groups", "offline_access"}
var validThemeNames = []string{"light", "dark", "grey", "auto"}
var validSessionSameSiteValues = []string{"none", "lax", "strict"}
var validLoLevels = []string{"trace", "debug", "info", "warn", "error"}
var validACLRuleMethods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
var validACLRulePolicies = []string{policyBypass, policyOneFactor, policyTwoFactor, policyDeny}
var validOIDCScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, "offline_access"}
var validOIDCGrantTypes = []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"}
var validOIDCResponseModes = []string{"form_post", "query", "fragment"}
var validOIDCUserinfoAlgorithms = []string{"none", "RS256"}

View File

@ -11,55 +11,55 @@ import (
)
// ValidateIdentityProviders validates and update IdentityProviders configuration.
func ValidateIdentityProviders(configuration *schema.IdentityProvidersConfiguration, validator *schema.StructValidator) {
validateOIDC(configuration.OIDC, validator)
func ValidateIdentityProviders(config *schema.IdentityProvidersConfiguration, validator *schema.StructValidator) {
validateOIDC(config.OIDC, validator)
}
func validateOIDC(configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
if configuration != nil {
if configuration.IssuerPrivateKey == "" {
func validateOIDC(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
if config != nil {
if config.IssuerPrivateKey == "" {
validator.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
}
if configuration.AccessTokenLifespan == time.Duration(0) {
configuration.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
if config.AccessTokenLifespan == time.Duration(0) {
config.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
}
if configuration.AuthorizeCodeLifespan == time.Duration(0) {
configuration.AuthorizeCodeLifespan = schema.DefaultOpenIDConnectConfiguration.AuthorizeCodeLifespan
if config.AuthorizeCodeLifespan == time.Duration(0) {
config.AuthorizeCodeLifespan = schema.DefaultOpenIDConnectConfiguration.AuthorizeCodeLifespan
}
if configuration.IDTokenLifespan == time.Duration(0) {
configuration.IDTokenLifespan = schema.DefaultOpenIDConnectConfiguration.IDTokenLifespan
if config.IDTokenLifespan == time.Duration(0) {
config.IDTokenLifespan = schema.DefaultOpenIDConnectConfiguration.IDTokenLifespan
}
if configuration.RefreshTokenLifespan == time.Duration(0) {
configuration.RefreshTokenLifespan = schema.DefaultOpenIDConnectConfiguration.RefreshTokenLifespan
if config.RefreshTokenLifespan == time.Duration(0) {
config.RefreshTokenLifespan = schema.DefaultOpenIDConnectConfiguration.RefreshTokenLifespan
}
if configuration.MinimumParameterEntropy != 0 && configuration.MinimumParameterEntropy < 8 {
validator.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, configuration.MinimumParameterEntropy))
if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
validator.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, config.MinimumParameterEntropy))
}
validateOIDCClients(configuration, validator)
validateOIDCClients(config, validator)
if len(configuration.Clients) == 0 {
if len(config.Clients) == 0 {
validator.Push(fmt.Errorf(errFmtOIDCNoClientsConfigured))
}
}
}
func validateOIDCClients(configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
func validateOIDCClients(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
invalidID, duplicateIDs := false, false
var ids []string
for c, client := range configuration.Clients {
for c, client := range config.Clients {
if client.ID == "" {
invalidID = true
} else {
if client.Description == "" {
configuration.Clients[c].Description = client.ID
config.Clients[c].Description = client.ID
}
if utils.IsStringInSliceFold(client.ID, ids) {
@ -79,16 +79,16 @@ func validateOIDCClients(configuration *schema.OpenIDConnectConfiguration, valid
}
if client.Policy == "" {
configuration.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy
config.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy
} else if client.Policy != policyOneFactor && client.Policy != policyTwoFactor {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy))
}
validateOIDCClientScopes(c, configuration, validator)
validateOIDCClientGrantTypes(c, configuration, validator)
validateOIDCClientResponseTypes(c, configuration, validator)
validateOIDCClientResponseModes(c, configuration, validator)
validateOIDDClientUserinfoAlgorithm(c, configuration, validator)
validateOIDCClientScopes(c, config, validator)
validateOIDCClientGrantTypes(c, config, validator)
validateOIDCClientResponseTypes(c, config, validator)
validateOIDCClientResponseModes(c, config, validator)
validateOIDDClientUserinfoAlgorithm(c, config, validator)
validateOIDCClientRedirectURIs(client, validator)
}
@ -115,8 +115,8 @@ func validateOIDCClientScopes(c int, configuration *schema.OpenIDConnectConfigur
for _, scope := range configuration.Clients[c].Scopes {
if !utils.IsStringInSlice(scope, validOIDCScopes) {
validator.Push(fmt.Errorf(
errFmtOIDCClientInvalidScope,
configuration.Clients[c].ID, scope, strings.Join(validOIDCScopes, "', '")))
errFmtOIDCClientInvalidEntry,
configuration.Clients[c].ID, "scopes", strings.Join(validOIDCScopes, "', '"), scope))
}
}
}
@ -130,8 +130,8 @@ func validateOIDCClientGrantTypes(c int, configuration *schema.OpenIDConnectConf
for _, grantType := range configuration.Clients[c].GrantTypes {
if !utils.IsStringInSlice(grantType, validOIDCGrantTypes) {
validator.Push(fmt.Errorf(
errFmtOIDCClientInvalidGrantType,
configuration.Clients[c].ID, grantType, strings.Join(validOIDCGrantTypes, "', '")))
errFmtOIDCClientInvalidEntry,
configuration.Clients[c].ID, "grant_types", strings.Join(validOIDCGrantTypes, "', '"), grantType))
}
}
}
@ -152,8 +152,8 @@ func validateOIDCClientResponseModes(c int, configuration *schema.OpenIDConnectC
for _, responseMode := range configuration.Clients[c].ResponseModes {
if !utils.IsStringInSlice(responseMode, validOIDCResponseModes) {
validator.Push(fmt.Errorf(
errFmtOIDCClientInvalidResponseMode,
configuration.Clients[c].ID, responseMode, strings.Join(validOIDCResponseModes, "', '")))
errFmtOIDCClientInvalidEntry,
configuration.Clients[c].ID, "response_modes", strings.Join(validOIDCResponseModes, "', '"), responseMode))
}
}
}
@ -163,7 +163,7 @@ func validateOIDDClientUserinfoAlgorithm(c int, configuration *schema.OpenIDConn
configuration.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlgorithm
} else if !utils.IsStringInSlice(configuration.Clients[c].UserinfoSigningAlgorithm, validOIDCUserinfoAlgorithms) {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidUserinfoAlgorithm,
configuration.Clients[c].ID, configuration.Clients[c].UserinfoSigningAlgorithm, strings.Join(validOIDCUserinfoAlgorithms, ", ")))
configuration.Clients[c].ID, strings.Join(validOIDCUserinfoAlgorithms, ", "), configuration.Clients[c].UserinfoSigningAlgorithm))
}
}
@ -174,7 +174,7 @@ func validateOIDCClientRedirectURIs(client schema.OpenIDConnectClientConfigurati
continue
}
validator.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, client.ID, redirectURI))
validator.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, client.ID, oauth2InstalledApp))
continue
}

View File

@ -173,8 +173,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadScopes(t *testing.T) {
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "openid connect provider: client with ID 'good_id' has an invalid scope "+
"'bad_scope', must be one of: 'openid', 'email', 'profile', 'groups', 'offline_access'")
assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', 'offline_access' but one option is configured as 'bad_scope'")
}
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T) {
@ -200,9 +199,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T)
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "openid connect provider: client with ID 'good_id' has an invalid grant type "+
"'bad_grant_type', must be one of: 'implicit', 'refresh_token', 'authorization_code', "+
"'password', 'client_credentials'")
assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'grant_types' must only have the values 'implicit', 'refresh_token', 'authorization_code', 'password', 'client_credentials' but one option is configured as 'bad_grant_type'")
}
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing.T) {
@ -228,8 +225,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "openid connect provider: client with ID 'good_id' has an invalid response mode "+
"'bad_responsemode', must be one of: 'form_post', 'query', 'fragment'")
assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'response_modes' must only have the values 'form_post', 'query', 'fragment' but one option is configured as 'bad_responsemode'")
}
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T) {
@ -255,8 +251,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "openid connect provider: client with ID 'good_id' has an invalid userinfo "+
"signing algorithm 'rs256', must be one of: 'none, RS256'")
assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'userinfo_signing_algorithm' must be one of 'none, RS256' but it is configured as 'rs256'")
}
func TestValidateIdentityProvidersShouldRaiseWarningOnSecurityIssue(t *testing.T) {
@ -502,8 +497,8 @@ func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 2)
assert.ElementsMatch(t, validator.Errors(), []error{
errors.New("openid connect provider: client with ID 'owncloud' redirect URI oc://ios.owncloud.com has an invalid scheme oc, should be http or https"),
errors.New("openid connect provider: client with ID 'owncloud' redirect URI com.example.app:/oauth2redirect/example-provider has an invalid scheme com.example.app, should be http or https"),
errors.New("identity_providers: oidc: client 'owncloud': option 'redirect_uris' has an invalid value: redirect uri 'oc://ios.owncloud.com' must have a scheme of 'http' or 'https' but 'oc' is configured"),
errors.New("identity_providers: oidc: client 'owncloud': option 'redirect_uris' has an invalid value: redirect uri 'com.example.app:/oauth2redirect/example-provider' must have a scheme of 'http' or 'https' but 'com.example.app' is configured"),
})
})
}

View File

@ -0,0 +1,24 @@
package validator
import (
"fmt"
"strings"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
)
// ValidateLog validates the logging configuration.
func ValidateLog(config *schema.Configuration, validator *schema.StructValidator) {
if config.Log.Level == "" {
config.Log.Level = schema.DefaultLoggingConfiguration.Level
}
if config.Log.Format == "" {
config.Log.Format = schema.DefaultLoggingConfiguration.Format
}
if !utils.IsStringInSlice(config.Log.Level, validLoLevels) {
validator.Push(fmt.Errorf(errFmtLoggingLevelInvalid, strings.Join(validLoLevels, "', '"), config.Log.Level))
}
}

View File

@ -14,7 +14,7 @@ func TestShouldSetDefaultLoggingValues(t *testing.T) {
validator := schema.NewStructValidator()
ValidateLogging(config, validator)
ValidateLog(config, validator)
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 0)
@ -35,10 +35,10 @@ func TestShouldRaiseErrorOnInvalidLoggingLevel(t *testing.T) {
validator := schema.NewStructValidator()
ValidateLogging(config, validator)
ValidateLog(config, validator)
assert.Len(t, validator.Warnings(), 0)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "the log level 'TRACE' is invalid, must be one of: trace, debug, info, warn, error")
assert.EqualError(t, validator.Errors()[0], "log: option 'level' must be one of 'trace', 'debug', 'info', 'warn', 'error' but it is configured as 'TRACE'")
}

View File

@ -1,24 +0,0 @@
package validator
import (
"fmt"
"strings"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
)
// ValidateLogging validates the logging configuration.
func ValidateLogging(configuration *schema.Configuration, validator *schema.StructValidator) {
if configuration.Log.Level == "" {
configuration.Log.Level = schema.DefaultLoggingConfiguration.Level
}
if configuration.Log.Format == "" {
configuration.Log.Format = schema.DefaultLoggingConfiguration.Format
}
if !utils.IsStringInSlice(configuration.Log.Level, validLoggingLevels) {
validator.Push(fmt.Errorf(errFmtLoggingLevelInvalid, configuration.Log.Level, strings.Join(validLoggingLevels, ", ")))
}
}

View File

@ -7,62 +7,62 @@ import (
)
// ValidateNotifier validates and update notifier configuration.
func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *schema.StructValidator) {
if configuration.SMTP == nil && configuration.FileSystem == nil {
func ValidateNotifier(config *schema.NotifierConfiguration, validator *schema.StructValidator) {
if config == nil || (config.SMTP == nil && config.FileSystem == nil) {
validator.Push(fmt.Errorf(errFmtNotifierNotConfigured))
return
} else if configuration.SMTP != nil && configuration.FileSystem != nil {
} else if config.SMTP != nil && config.FileSystem != nil {
validator.Push(fmt.Errorf(errFmtNotifierMultipleConfigured))
return
}
if configuration.FileSystem != nil {
if configuration.FileSystem.Filename == "" {
if config.FileSystem != nil {
if config.FileSystem.Filename == "" {
validator.Push(fmt.Errorf(errFmtNotifierFileSystemFileNameNotConfigured))
}
return
}
validateSMTPNotifier(configuration.SMTP, validator)
validateSMTPNotifier(config.SMTP, validator)
}
func validateSMTPNotifier(configuration *schema.SMTPNotifierConfiguration, validator *schema.StructValidator) {
if configuration.StartupCheckAddress == "" {
configuration.StartupCheckAddress = schema.DefaultSMTPNotifierConfiguration.StartupCheckAddress
func validateSMTPNotifier(config *schema.SMTPNotifierConfiguration, validator *schema.StructValidator) {
if config.StartupCheckAddress == "" {
config.StartupCheckAddress = schema.DefaultSMTPNotifierConfiguration.StartupCheckAddress
}
if configuration.Host == "" {
if config.Host == "" {
validator.Push(fmt.Errorf(errFmtNotifierSMTPNotConfigured, "host"))
}
if configuration.Port == 0 {
if config.Port == 0 {
validator.Push(fmt.Errorf(errFmtNotifierSMTPNotConfigured, "port"))
}
if configuration.Timeout == 0 {
configuration.Timeout = schema.DefaultSMTPNotifierConfiguration.Timeout
if config.Timeout == 0 {
config.Timeout = schema.DefaultSMTPNotifierConfiguration.Timeout
}
if configuration.Sender.Address == "" {
if config.Sender.Address == "" {
validator.Push(fmt.Errorf(errFmtNotifierSMTPNotConfigured, "sender"))
}
if configuration.Subject == "" {
configuration.Subject = schema.DefaultSMTPNotifierConfiguration.Subject
if config.Subject == "" {
config.Subject = schema.DefaultSMTPNotifierConfiguration.Subject
}
if configuration.Identifier == "" {
configuration.Identifier = schema.DefaultSMTPNotifierConfiguration.Identifier
if config.Identifier == "" {
config.Identifier = schema.DefaultSMTPNotifierConfiguration.Identifier
}
if configuration.TLS == nil {
configuration.TLS = schema.DefaultSMTPNotifierConfiguration.TLS
if config.TLS == nil {
config.TLS = schema.DefaultSMTPNotifierConfiguration.TLS
}
if configuration.TLS.ServerName == "" {
configuration.TLS.ServerName = configuration.Host
if config.TLS.ServerName == "" {
config.TLS.ServerName = config.Host
}
}

View File

@ -12,34 +12,34 @@ import (
type NotifierSuite struct {
suite.Suite
configuration schema.NotifierConfiguration
validator *schema.StructValidator
config schema.NotifierConfiguration
validator *schema.StructValidator
}
func (suite *NotifierSuite) SetupTest() {
suite.validator = schema.NewStructValidator()
suite.configuration.SMTP = &schema.SMTPNotifierConfiguration{
suite.config.SMTP = &schema.SMTPNotifierConfiguration{
Username: "john",
Password: "password",
Sender: mail.Address{Name: "Authelia", Address: "authelia@example.com"},
Host: "example.com",
Port: 25,
}
suite.configuration.FileSystem = nil
suite.config.FileSystem = nil
}
/*
Common Tests.
*/
func (suite *NotifierSuite) TestShouldEnsureAtLeastSMTPOrFilesystemIsProvided() {
ValidateNotifier(&suite.configuration, suite.validator)
ValidateNotifier(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.configuration.SMTP = nil
suite.config.SMTP = nil
ValidateNotifier(&suite.configuration, suite.validator)
ValidateNotifier(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().True(suite.validator.HasErrors())
@ -50,15 +50,15 @@ func (suite *NotifierSuite) TestShouldEnsureAtLeastSMTPOrFilesystemIsProvided()
}
func (suite *NotifierSuite) TestShouldEnsureEitherSMTPOrFilesystemIsProvided() {
ValidateNotifier(&suite.configuration, suite.validator)
ValidateNotifier(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasErrors())
suite.configuration.FileSystem = &schema.FileSystemNotifierConfiguration{
suite.config.FileSystem = &schema.FileSystemNotifierConfiguration{
Filename: "test",
}
ValidateNotifier(&suite.configuration, suite.validator)
ValidateNotifier(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().True(suite.validator.HasErrors())
@ -72,43 +72,43 @@ func (suite *NotifierSuite) TestShouldEnsureEitherSMTPOrFilesystemIsProvided() {
SMTP Tests.
*/
func (suite *NotifierSuite) TestSMTPShouldSetTLSDefaults() {
ValidateNotifier(&suite.configuration, suite.validator)
ValidateNotifier(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.Assert().Equal("example.com", suite.configuration.SMTP.TLS.ServerName)
suite.Assert().Equal("TLS1.2", suite.configuration.SMTP.TLS.MinimumVersion)
suite.Assert().False(suite.configuration.SMTP.TLS.SkipVerify)
suite.Assert().Equal("example.com", suite.config.SMTP.TLS.ServerName)
suite.Assert().Equal("TLS1.2", suite.config.SMTP.TLS.MinimumVersion)
suite.Assert().False(suite.config.SMTP.TLS.SkipVerify)
}
func (suite *NotifierSuite) TestSMTPShouldDefaultTLSServerNameToHost() {
suite.configuration.SMTP.Host = "google.com"
suite.configuration.SMTP.TLS = &schema.TLSConfig{
suite.config.SMTP.Host = "google.com"
suite.config.SMTP.TLS = &schema.TLSConfig{
MinimumVersion: "TLS1.1",
}
ValidateNotifier(&suite.configuration, suite.validator)
ValidateNotifier(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.Assert().Equal("google.com", suite.configuration.SMTP.TLS.ServerName)
suite.Assert().Equal("TLS1.1", suite.configuration.SMTP.TLS.MinimumVersion)
suite.Assert().False(suite.configuration.SMTP.TLS.SkipVerify)
suite.Assert().Equal("google.com", suite.config.SMTP.TLS.ServerName)
suite.Assert().Equal("TLS1.1", suite.config.SMTP.TLS.MinimumVersion)
suite.Assert().False(suite.config.SMTP.TLS.SkipVerify)
}
func (suite *NotifierSuite) TestSMTPShouldEnsureHostAndPortAreProvided() {
suite.configuration.FileSystem = nil
ValidateNotifier(&suite.configuration, suite.validator)
suite.config.FileSystem = nil
ValidateNotifier(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.configuration.SMTP.Host = ""
suite.configuration.SMTP.Port = 0
suite.config.SMTP.Host = ""
suite.config.SMTP.Port = 0
ValidateNotifier(&suite.configuration, suite.validator)
ValidateNotifier(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().True(suite.validator.HasErrors())
@ -122,9 +122,9 @@ func (suite *NotifierSuite) TestSMTPShouldEnsureHostAndPortAreProvided() {
}
func (suite *NotifierSuite) TestSMTPShouldEnsureSenderIsProvided() {
suite.configuration.SMTP.Sender = mail.Address{}
suite.config.SMTP.Sender = mail.Address{}
ValidateNotifier(&suite.configuration, suite.validator)
ValidateNotifier(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().True(suite.validator.HasErrors())
@ -138,18 +138,18 @@ func (suite *NotifierSuite) TestSMTPShouldEnsureSenderIsProvided() {
File Tests.
*/
func (suite *NotifierSuite) TestFileShouldEnsureFilenameIsProvided() {
suite.configuration.SMTP = nil
suite.configuration.FileSystem = &schema.FileSystemNotifierConfiguration{
suite.config.SMTP = nil
suite.config.FileSystem = &schema.FileSystemNotifierConfiguration{
Filename: "test",
}
ValidateNotifier(&suite.configuration, suite.validator)
ValidateNotifier(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.configuration.FileSystem.Filename = ""
suite.config.FileSystem.Filename = ""
ValidateNotifier(&suite.configuration, suite.validator)
ValidateNotifier(&suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().True(suite.validator.HasErrors())

View File

@ -8,23 +8,29 @@ import (
)
// ValidateNTP validates and update NTP configuration.
func ValidateNTP(configuration *schema.NTPConfiguration, validator *schema.StructValidator) {
if configuration.Address == "" {
configuration.Address = schema.DefaultNTPConfiguration.Address
func ValidateNTP(config *schema.Configuration, validator *schema.StructValidator) {
if config.NTP == nil {
config.NTP = &schema.DefaultNTPConfiguration
return
}
if configuration.Version == 0 {
configuration.Version = schema.DefaultNTPConfiguration.Version
} else if configuration.Version < 3 || configuration.Version > 4 {
validator.Push(fmt.Errorf("ntp: version must be either 3 or 4"))
if config.NTP.Address == "" {
config.NTP.Address = schema.DefaultNTPConfiguration.Address
}
if configuration.MaximumDesync == "" {
configuration.MaximumDesync = schema.DefaultNTPConfiguration.MaximumDesync
if config.NTP.Version == 0 {
config.NTP.Version = schema.DefaultNTPConfiguration.Version
} else if config.NTP.Version < 3 || config.NTP.Version > 4 {
validator.Push(fmt.Errorf(errFmtNTPVersion, config.NTP.Version))
}
_, err := utils.ParseDurationString(configuration.MaximumDesync)
if config.NTP.MaximumDesync == "" {
config.NTP.MaximumDesync = schema.DefaultNTPConfiguration.MaximumDesync
}
_, err := utils.ParseDurationString(config.NTP.MaximumDesync)
if err != nil {
validator.Push(fmt.Errorf("ntp: error occurred parsing NTP max_desync string: %s", err))
validator.Push(fmt.Errorf(errFmtNTPMaxDesync, err))
}
}

View File

@ -4,13 +4,15 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
func newDefaultNTPConfig() schema.NTPConfiguration {
config := schema.NTPConfiguration{}
return config
func newDefaultNTPConfig() schema.Configuration {
return schema.Configuration{
NTP: &schema.NTPConfiguration{},
}
}
func TestShouldSetDefaultNtpAddress(t *testing.T) {
@ -20,7 +22,7 @@ func TestShouldSetDefaultNtpAddress(t *testing.T) {
ValidateNTP(&config, validator)
assert.Len(t, validator.Errors(), 0)
assert.Equal(t, schema.DefaultNTPConfiguration.Address, config.Address)
assert.Equal(t, schema.DefaultNTPConfiguration.Address, config.NTP.Address)
}
func TestShouldSetDefaultNtpVersion(t *testing.T) {
@ -30,7 +32,7 @@ func TestShouldSetDefaultNtpVersion(t *testing.T) {
ValidateNTP(&config, validator)
assert.Len(t, validator.Errors(), 0)
assert.Equal(t, schema.DefaultNTPConfiguration.Version, config.Version)
assert.Equal(t, schema.DefaultNTPConfiguration.Version, config.NTP.Version)
}
func TestShouldSetDefaultNtpMaximumDesync(t *testing.T) {
@ -40,7 +42,7 @@ func TestShouldSetDefaultNtpMaximumDesync(t *testing.T) {
ValidateNTP(&config, validator)
assert.Len(t, validator.Errors(), 0)
assert.Equal(t, schema.DefaultNTPConfiguration.MaximumDesync, config.MaximumDesync)
assert.Equal(t, schema.DefaultNTPConfiguration.MaximumDesync, config.NTP.MaximumDesync)
}
func TestShouldSetDefaultNtpDisableStartupCheck(t *testing.T) {
@ -50,16 +52,29 @@ func TestShouldSetDefaultNtpDisableStartupCheck(t *testing.T) {
ValidateNTP(&config, validator)
assert.Len(t, validator.Errors(), 0)
assert.Equal(t, schema.DefaultNTPConfiguration.DisableStartupCheck, config.DisableStartupCheck)
assert.Equal(t, schema.DefaultNTPConfiguration.DisableStartupCheck, config.NTP.DisableStartupCheck)
}
func TestShouldRaiseErrorOnMaximumDesyncString(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultNTPConfig()
config.MaximumDesync = "a second"
config.NTP.MaximumDesync = "a second"
ValidateNTP(&config, validator)
assert.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "ntp: error occurred parsing NTP max_desync string: could not convert the input string of a second into a duration")
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "ntp: option 'max_desync' can't be parsed: could not parse 'a second' as a duration")
}
func TestShouldRaiseErrorOnInvalidNTPVersion(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultNTPConfig()
config.NTP.Version = 1
ValidateNTP(&config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "ntp: option 'version' must be either 3 or 4 but it is configured as '1'")
}

View File

@ -8,26 +8,32 @@ import (
)
// ValidateRegulation validates and update regulator configuration.
func ValidateRegulation(configuration *schema.RegulationConfiguration, validator *schema.StructValidator) {
if configuration.FindTime == "" {
configuration.FindTime = schema.DefaultRegulationConfiguration.FindTime // 2 min.
func ValidateRegulation(config *schema.Configuration, validator *schema.StructValidator) {
if config.Regulation == nil {
config.Regulation = &schema.DefaultRegulationConfiguration
return
}
if configuration.BanTime == "" {
configuration.BanTime = schema.DefaultRegulationConfiguration.BanTime // 5 min.
if config.Regulation.FindTime == "" {
config.Regulation.FindTime = schema.DefaultRegulationConfiguration.FindTime // 2 min.
}
findTime, err := utils.ParseDurationString(configuration.FindTime)
if config.Regulation.BanTime == "" {
config.Regulation.BanTime = schema.DefaultRegulationConfiguration.BanTime // 5 min.
}
findTime, err := utils.ParseDurationString(config.Regulation.FindTime)
if err != nil {
validator.Push(fmt.Errorf("Error occurred parsing regulation find_time string: %s", err))
validator.Push(fmt.Errorf(errFmtRegulationParseDuration, "find_time", err))
}
banTime, err := utils.ParseDurationString(configuration.BanTime)
banTime, err := utils.ParseDurationString(config.Regulation.BanTime)
if err != nil {
validator.Push(fmt.Errorf("Error occurred parsing regulation ban_time string: %s", err))
validator.Push(fmt.Errorf(errFmtRegulationParseDuration, "ban_time", err))
}
if findTime > banTime {
validator.Push(fmt.Errorf("find_time cannot be greater than ban_time"))
validator.Push(fmt.Errorf(errFmtRegulationFindTimeGreaterThanBanTime))
}
}

View File

@ -8,8 +8,11 @@ import (
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
func newDefaultRegulationConfig() schema.RegulationConfiguration {
config := schema.RegulationConfiguration{}
func newDefaultRegulationConfig() schema.Configuration {
config := schema.Configuration{
Regulation: &schema.RegulationConfiguration{},
}
return config
}
@ -20,7 +23,7 @@ func TestShouldSetDefaultRegulationBanTime(t *testing.T) {
ValidateRegulation(&config, validator)
assert.Len(t, validator.Errors(), 0)
assert.Equal(t, schema.DefaultRegulationConfiguration.BanTime, config.BanTime)
assert.Equal(t, schema.DefaultRegulationConfiguration.BanTime, config.Regulation.BanTime)
}
func TestShouldSetDefaultRegulationFindTime(t *testing.T) {
@ -30,30 +33,30 @@ func TestShouldSetDefaultRegulationFindTime(t *testing.T) {
ValidateRegulation(&config, validator)
assert.Len(t, validator.Errors(), 0)
assert.Equal(t, schema.DefaultRegulationConfiguration.FindTime, config.FindTime)
assert.Equal(t, schema.DefaultRegulationConfiguration.FindTime, config.Regulation.FindTime)
}
func TestShouldRaiseErrorWhenFindTimeLessThanBanTime(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultRegulationConfig()
config.FindTime = "1m"
config.BanTime = "10s"
config.Regulation.FindTime = "1m"
config.Regulation.BanTime = "10s"
ValidateRegulation(&config, validator)
assert.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "find_time cannot be greater than ban_time")
assert.EqualError(t, validator.Errors()[0], "regulation: option 'find_time' must be less than or equal to option 'ban_time'")
}
func TestShouldRaiseErrorOnBadDurationStrings(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultRegulationConfig()
config.FindTime = "a year"
config.BanTime = "forever"
config.Regulation.FindTime = "a year"
config.Regulation.BanTime = "forever"
ValidateRegulation(&config, validator)
assert.Len(t, validator.Errors(), 2)
assert.EqualError(t, validator.Errors()[0], "Error occurred parsing regulation find_time string: could not convert the input string of a year into a duration")
assert.EqualError(t, validator.Errors()[1], "Error occurred parsing regulation ban_time string: could not convert the input string of forever into a duration")
assert.EqualError(t, validator.Errors()[0], "regulation: option 'find_time' could not be parsed: could not parse 'a year' as a duration")
assert.EqualError(t, validator.Errors()[1], "regulation: option 'ban_time' could not be parsed: could not parse 'forever' as a duration")
}

View File

@ -10,40 +10,41 @@ import (
)
// ValidateServer checks a server configuration is correct.
func ValidateServer(configuration *schema.Configuration, validator *schema.StructValidator) {
if configuration.Server.Host == "" {
configuration.Server.Host = schema.DefaultServerConfiguration.Host
func ValidateServer(config *schema.Configuration, validator *schema.StructValidator) {
if config.Server.Host == "" {
config.Server.Host = schema.DefaultServerConfiguration.Host
}
if configuration.Server.Port == 0 {
configuration.Server.Port = schema.DefaultServerConfiguration.Port
if config.Server.Port == 0 {
config.Server.Port = schema.DefaultServerConfiguration.Port
}
if configuration.Server.TLS.Key != "" && configuration.Server.TLS.Certificate == "" {
validator.Push(fmt.Errorf("server: no TLS certificate provided to accompany the TLS key, please configure the 'server.tls.certificate' option"))
} else if configuration.Server.TLS.Key == "" && configuration.Server.TLS.Certificate != "" {
validator.Push(fmt.Errorf("server: no TLS key provided to accompany the TLS certificate, please configure the 'server.tls.key' option"))
if config.Server.TLS.Key != "" && config.Server.TLS.Certificate == "" {
validator.Push(fmt.Errorf(errFmtServerTLSCert))
} else if config.Server.TLS.Key == "" && config.Server.TLS.Certificate != "" {
validator.Push(fmt.Errorf(errFmtServerTLSKey))
}
switch {
case strings.Contains(configuration.Server.Path, "/"):
validator.Push(fmt.Errorf("server path must not contain any forward slashes"))
case !utils.IsStringAlphaNumeric(configuration.Server.Path):
validator.Push(fmt.Errorf("server path must only be alpha numeric characters"))
case configuration.Server.Path == "": // Don't do anything if it's blank.
case strings.Contains(config.Server.Path, "/"):
validator.Push(fmt.Errorf(errFmtServerPathNoForwardSlashes))
case !utils.IsStringAlphaNumeric(config.Server.Path):
validator.Push(fmt.Errorf(errFmtServerPathAlphaNum))
case config.Server.Path == "": // Don't do anything if it's blank.
break
default:
configuration.Server.Path = path.Clean("/" + configuration.Server.Path)
config.Server.Path = path.Clean("/" + config.Server.Path)
}
if configuration.Server.ReadBufferSize == 0 {
configuration.Server.ReadBufferSize = schema.DefaultServerConfiguration.ReadBufferSize
} else if configuration.Server.ReadBufferSize < 0 {
validator.Push(fmt.Errorf("server read buffer size must be above 0"))
if config.Server.ReadBufferSize == 0 {
config.Server.ReadBufferSize = schema.DefaultServerConfiguration.ReadBufferSize
} else if config.Server.ReadBufferSize < 0 {
validator.Push(fmt.Errorf(errFmtServerBufferSize, "read", config.Server.ReadBufferSize))
}
if configuration.Server.WriteBufferSize == 0 {
configuration.Server.WriteBufferSize = schema.DefaultServerConfiguration.WriteBufferSize
} else if configuration.Server.WriteBufferSize < 0 {
validator.Push(fmt.Errorf("server write buffer size must be above 0"))
if config.Server.WriteBufferSize == 0 {
config.Server.WriteBufferSize = schema.DefaultServerConfiguration.WriteBufferSize
} else if config.Server.WriteBufferSize < 0 {
validator.Push(fmt.Errorf(errFmtServerBufferSize, "write", config.Server.WriteBufferSize))
}
}

View File

@ -71,8 +71,8 @@ func TestShouldRaiseOnNegativeValues(t *testing.T) {
require.Len(t, validator.Errors(), 2)
assert.EqualError(t, validator.Errors()[0], "server read buffer size must be above 0")
assert.EqualError(t, validator.Errors()[1], "server write buffer size must be above 0")
assert.EqualError(t, validator.Errors()[0], "server: option 'read_buffer_size' must be above 0 but it is configured as '-1'")
assert.EqualError(t, validator.Errors()[1], "server: option 'write_buffer_size' must be above 0 but it is configured as '-1'")
}
func TestShouldRaiseOnNonAlphanumericCharsInPath(t *testing.T) {
@ -123,7 +123,7 @@ func TestShouldRaiseErrorWhenTLSCertWithoutKeyIsProvided(t *testing.T) {
ValidateServer(&config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "server: no TLS key provided to accompany the TLS certificate, please configure the 'server.tls.key' option")
assert.EqualError(t, validator.Errors()[0], "server: tls: option 'certificate' must also be accompanied by option 'key'")
}
func TestShouldRaiseErrorWhenTLSKeyWithoutCertIsProvided(t *testing.T) {
@ -133,7 +133,7 @@ func TestShouldRaiseErrorWhenTLSKeyWithoutCertIsProvided(t *testing.T) {
ValidateServer(&config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "server: no TLS certificate provided to accompany the TLS key, please configure the 'server.tls.certificate' option")
assert.EqualError(t, validator.Errors()[0], "server: tls: option 'key' must also be accompanied by option 'certificate'")
}
func TestShouldNotRaiseErrorWhenBothTLSCertificateAndKeyAreProvided(t *testing.T) {

View File

@ -10,109 +10,110 @@ import (
)
// ValidateSession validates and update session configuration.
func ValidateSession(configuration *schema.SessionConfiguration, validator *schema.StructValidator) {
if configuration.Name == "" {
configuration.Name = schema.DefaultSessionConfiguration.Name
func ValidateSession(config *schema.SessionConfiguration, validator *schema.StructValidator) {
if config.Name == "" {
config.Name = schema.DefaultSessionConfiguration.Name
}
if configuration.Redis != nil {
if configuration.Redis.HighAvailability != nil {
if configuration.Redis.HighAvailability.SentinelName != "" {
validateRedisSentinel(configuration, validator)
} else {
validator.Push(fmt.Errorf("Session provider redis is configured for high availability but doesn't have a sentinel_name which is required"))
}
if config.Redis != nil {
if config.Redis.HighAvailability != nil {
validateRedisSentinel(config, validator)
} else {
validateRedis(configuration, validator)
validateRedis(config, validator)
}
}
validateSession(configuration, validator)
validateSession(config, validator)
}
func validateSession(configuration *schema.SessionConfiguration, validator *schema.StructValidator) {
if configuration.Expiration == "" {
configuration.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour.
} else if _, err := utils.ParseDurationString(configuration.Expiration); err != nil {
validator.Push(fmt.Errorf("Error occurred parsing session expiration string: %s", err))
func validateSession(config *schema.SessionConfiguration, validator *schema.StructValidator) {
if config.Expiration == "" {
config.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour.
} else if _, err := utils.ParseDurationString(config.Expiration); err != nil {
validator.Push(fmt.Errorf(errFmtSessionCouldNotParseDuration, "expiriation", err))
}
if configuration.Inactivity == "" {
configuration.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min.
} else if _, err := utils.ParseDurationString(configuration.Inactivity); err != nil {
validator.Push(fmt.Errorf("Error occurred parsing session inactivity string: %s", err))
if config.Inactivity == "" {
config.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min.
} else if _, err := utils.ParseDurationString(config.Inactivity); err != nil {
validator.Push(fmt.Errorf(errFmtSessionCouldNotParseDuration, "inactivity", err))
}
if configuration.RememberMeDuration == "" {
configuration.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration // 1 month.
} else if _, err := utils.ParseDurationString(configuration.RememberMeDuration); err != nil {
validator.Push(fmt.Errorf("Error occurred parsing session remember_me_duration string: %s", err))
if config.RememberMeDuration == "" {
config.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration // 1 month.
} else if _, err := utils.ParseDurationString(config.RememberMeDuration); err != nil {
validator.Push(fmt.Errorf(errFmtSessionCouldNotParseDuration, "remember_me_duration", err))
}
if configuration.Domain == "" {
validator.Push(errors.New("Set domain of the session object"))
if config.Domain == "" {
validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "domain"))
}
if strings.Contains(configuration.Domain, "*") {
validator.Push(errors.New("The domain of the session must be the root domain you're protecting instead of a wildcard domain"))
if strings.HasPrefix(config.Domain, "*.") {
validator.Push(fmt.Errorf(errFmtSessionDomainMustBeRoot, config.Domain))
}
if configuration.SameSite == "" {
configuration.SameSite = schema.DefaultSessionConfiguration.SameSite
} else if configuration.SameSite != "none" && configuration.SameSite != "lax" && configuration.SameSite != "strict" {
validator.Push(errors.New("session same_site is configured incorrectly, must be one of 'none', 'lax', or 'strict'"))
if config.SameSite == "" {
config.SameSite = schema.DefaultSessionConfiguration.SameSite
} else if !utils.IsStringInSlice(config.SameSite, validSessionSameSiteValues) {
validator.Push(fmt.Errorf(errFmtSessionSameSite, strings.Join(validSessionSameSiteValues, "', '"), config.SameSite))
}
}
func validateRedis(configuration *schema.SessionConfiguration, validator *schema.StructValidator) {
if configuration.Redis.Host == "" {
validator.Push(fmt.Errorf(errFmtSessionRedisHostRequired, "redis"))
func validateRedisCommon(config *schema.SessionConfiguration, validator *schema.StructValidator) {
if config.Secret == "" {
validator.Push(fmt.Errorf(errFmtSessionSecretRequired, "redis"))
}
}
func validateRedis(config *schema.SessionConfiguration, validator *schema.StructValidator) {
if config.Redis.Host == "" {
validator.Push(fmt.Errorf(errFmtSessionRedisHostRequired))
}
if configuration.Secret == "" {
validator.Push(fmt.Errorf(errFmtSessionSecretRedisProvider, "redis"))
}
validateRedisCommon(config, validator)
if !strings.HasPrefix(configuration.Redis.Host, "/") && configuration.Redis.Port == 0 {
if !strings.HasPrefix(config.Redis.Host, "/") && config.Redis.Port == 0 {
validator.Push(errors.New("A redis port different than 0 must be provided"))
} else if configuration.Redis.Port < 0 || configuration.Redis.Port > 65535 {
validator.Push(fmt.Errorf(errFmtSessionRedisPortRange, "redis"))
} else if config.Redis.Port < 0 || config.Redis.Port > 65535 {
validator.Push(fmt.Errorf(errFmtSessionRedisPortRange, config.Redis.Port))
}
if configuration.Redis.MaximumActiveConnections <= 0 {
configuration.Redis.MaximumActiveConnections = 8
if config.Redis.MaximumActiveConnections <= 0 {
config.Redis.MaximumActiveConnections = 8
}
}
func validateRedisSentinel(configuration *schema.SessionConfiguration, validator *schema.StructValidator) {
if configuration.Redis.Port == 0 {
configuration.Redis.Port = 26379
} else if configuration.Redis.Port < 0 || configuration.Redis.Port > 65535 {
validator.Push(fmt.Errorf(errFmtSessionRedisPortRange, "redis sentinel"))
func validateRedisSentinel(config *schema.SessionConfiguration, validator *schema.StructValidator) {
if config.Redis.HighAvailability.SentinelName == "" {
validator.Push(fmt.Errorf(errFmtSessionRedisSentinelMissingName))
}
validateHighAvailability(configuration, validator, "redis sentinel")
}
func validateHighAvailability(configuration *schema.SessionConfiguration, validator *schema.StructValidator, provider string) {
if configuration.Redis.Host == "" && len(configuration.Redis.HighAvailability.Nodes) == 0 {
validator.Push(fmt.Errorf(errFmtSessionRedisHostOrNodesRequired, provider))
if config.Redis.Port == 0 {
config.Redis.Port = 26379
} else if config.Redis.Port < 0 || config.Redis.Port > 65535 {
validator.Push(fmt.Errorf(errFmtSessionRedisPortRange, config.Redis.Port))
}
if configuration.Secret == "" {
validator.Push(fmt.Errorf(errFmtSessionSecretRedisProvider, provider))
if config.Redis.Host == "" && len(config.Redis.HighAvailability.Nodes) == 0 {
validator.Push(fmt.Errorf(errFmtSessionRedisHostOrNodesRequired))
}
for i, node := range configuration.Redis.HighAvailability.Nodes {
validateRedisCommon(config, validator)
hostMissing := false
for i, node := range config.Redis.HighAvailability.Nodes {
if node.Host == "" {
validator.Push(fmt.Errorf("The %s nodes require a host set but you have not set the host for one or more nodes", provider))
break
hostMissing = true
}
if node.Port == 0 {
if provider == "redis sentinel" {
configuration.Redis.HighAvailability.Nodes[i].Port = 26379
}
config.Redis.HighAvailability.Nodes[i].Port = 26379
}
}
if hostMissing {
validator.Push(fmt.Errorf(errFmtSessionRedisSentinelNodeHostMissing))
}
}

View File

@ -100,7 +100,7 @@ func TestShouldRaiseErrorWithInvalidRedisPortLow(t *testing.T) {
assert.False(t, validator.HasWarnings())
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionRedisPortRange, "redis"))
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionRedisPortRange, -1))
}
func TestShouldRaiseErrorWithInvalidRedisPortHigh(t *testing.T) {
@ -117,7 +117,7 @@ func TestShouldRaiseErrorWithInvalidRedisPortHigh(t *testing.T) {
assert.False(t, validator.HasWarnings())
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionRedisPortRange, "redis"))
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionRedisPortRange, 65536))
}
func TestShouldRaiseErrorWhenRedisIsUsedAndSecretNotSet(t *testing.T) {
@ -140,7 +140,7 @@ func TestShouldRaiseErrorWhenRedisIsUsedAndSecretNotSet(t *testing.T) {
assert.False(t, validator.HasWarnings())
assert.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionSecretRedisProvider, "redis"))
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionSecretRequired, "redis"))
}
func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) {
@ -193,7 +193,7 @@ func TestShouldRaiseOneErrorWhenRedisHighAvailabilityHasNodesWithNoHost(t *testi
assert.False(t, validator.HasWarnings())
require.Len(t, errors, 1)
assert.EqualError(t, errors[0], "The redis sentinel nodes require a host set but you have not set the host for one or more nodes")
assert.EqualError(t, errors[0], "session: redis: high_availability: option 'nodes': option 'host' is required for each node but one or more nodes are missing this")
}
func TestShouldRaiseOneErrorWhenRedisHighAvailabilityDoesNotHaveSentinelName(t *testing.T) {
@ -215,7 +215,7 @@ func TestShouldRaiseOneErrorWhenRedisHighAvailabilityDoesNotHaveSentinelName(t *
assert.False(t, validator.HasWarnings())
require.Len(t, errors, 1)
assert.EqualError(t, errors[0], "Session provider redis is configured for high availability but doesn't have a sentinel_name which is required")
assert.EqualError(t, errors[0], "session: redis: high_availability: option 'sentinel_name' is required")
}
func TestShouldUpdateDefaultPortWhenRedisSentinelHasNodes(t *testing.T) {
@ -281,8 +281,8 @@ func TestShouldRaiseErrorsWhenRedisSentinelOptionsIncorrectlyConfigured(t *testi
assert.False(t, validator.HasWarnings())
require.Len(t, errors, 2)
assert.EqualError(t, errors[0], fmt.Sprintf(errFmtSessionRedisPortRange, "redis sentinel"))
assert.EqualError(t, errors[1], fmt.Sprintf(errFmtSessionSecretRedisProvider, "redis sentinel"))
assert.EqualError(t, errors[0], fmt.Sprintf(errFmtSessionRedisPortRange, 65536))
assert.EqualError(t, errors[1], fmt.Sprintf(errFmtSessionSecretRequired, "redis"))
validator.Clear()
@ -295,8 +295,8 @@ func TestShouldRaiseErrorsWhenRedisSentinelOptionsIncorrectlyConfigured(t *testi
assert.False(t, validator.HasWarnings())
require.Len(t, errors, 2)
assert.EqualError(t, errors[0], fmt.Sprintf(errFmtSessionRedisPortRange, "redis sentinel"))
assert.EqualError(t, errors[1], fmt.Sprintf(errFmtSessionSecretRedisProvider, "redis sentinel"))
assert.EqualError(t, errors[0], fmt.Sprintf(errFmtSessionRedisPortRange, -1))
assert.EqualError(t, errors[1], fmt.Sprintf(errFmtSessionSecretRequired, "redis"))
}
func TestShouldNotRaiseErrorsAndSetDefaultPortWhenRedisSentinelPortBlank(t *testing.T) {
@ -347,7 +347,7 @@ func TestShouldRaiseErrorWhenRedisHostAndHighAvailabilityNodesEmpty(t *testing.T
assert.False(t, validator.HasWarnings())
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionRedisHostOrNodesRequired, "redis sentinel"))
assert.EqualError(t, validator.Errors()[0], errFmtSessionRedisHostOrNodesRequired)
}
func TestShouldRaiseErrorsWhenRedisHostNotSet(t *testing.T) {
@ -365,7 +365,7 @@ func TestShouldRaiseErrorsWhenRedisHostNotSet(t *testing.T) {
assert.False(t, validator.HasWarnings())
require.Len(t, errors, 1)
assert.EqualError(t, errors[0], fmt.Sprintf(errFmtSessionRedisHostRequired, "redis"))
assert.EqualError(t, errors[0], errFmtSessionRedisHostRequired)
}
func TestShouldRaiseErrorWhenDomainNotSet(t *testing.T) {
@ -377,7 +377,7 @@ func TestShouldRaiseErrorWhenDomainNotSet(t *testing.T) {
assert.False(t, validator.HasWarnings())
assert.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "Set domain of the session object")
assert.EqualError(t, validator.Errors()[0], "session: option 'domain' is required")
}
func TestShouldRaiseErrorWhenDomainIsWildcard(t *testing.T) {
@ -389,7 +389,7 @@ func TestShouldRaiseErrorWhenDomainIsWildcard(t *testing.T) {
assert.False(t, validator.HasWarnings())
assert.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "The domain of the session must be the root domain you're protecting instead of a wildcard domain")
assert.EqualError(t, validator.Errors()[0], "session: option 'domain' must be the domain you wish to protect not a wildcard domain but it is configured as '*.example.com'")
}
func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) {
@ -401,7 +401,7 @@ func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) {
assert.False(t, validator.HasWarnings())
assert.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "session same_site is configured incorrectly, must be one of 'none', 'lax', or 'strict'")
assert.EqualError(t, validator.Errors()[0], "session: option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'NOne'")
}
func TestShouldNotRaiseErrorWhenSameSiteSetCorrectly(t *testing.T) {
@ -430,8 +430,8 @@ func TestShouldRaiseErrorWhenBadInactivityAndExpirationSet(t *testing.T) {
assert.False(t, validator.HasWarnings())
assert.Len(t, validator.Errors(), 2)
assert.EqualError(t, validator.Errors()[0], "Error occurred parsing session expiration string: could not convert the input string of -1 into a duration")
assert.EqualError(t, validator.Errors()[1], "Error occurred parsing session inactivity string: could not convert the input string of -1 into a duration")
assert.EqualError(t, validator.Errors()[0], "session: option 'expiriation' could not be parsed: could not parse '-1' as a duration")
assert.EqualError(t, validator.Errors()[1], "session: option 'inactivity' could not be parsed: could not parse '-1' as a duration")
}
func TestShouldRaiseErrorWhenBadRememberMeDurationSet(t *testing.T) {
@ -443,7 +443,7 @@ func TestShouldRaiseErrorWhenBadRememberMeDurationSet(t *testing.T) {
assert.False(t, validator.HasWarnings())
assert.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "Error occurred parsing session remember_me_duration string: could not convert the input string of 1 year into a duration")
assert.EqualError(t, validator.Errors()[0], "session: option 'remember_me_duration' could not be parsed: could not parse '1 year' as a duration")
}
func TestShouldSetDefaultRememberMeDuration(t *testing.T) {

View File

@ -10,66 +10,66 @@ import (
)
// ValidateStorage validates storage configuration.
func ValidateStorage(configuration schema.StorageConfiguration, validator *schema.StructValidator) {
if configuration.Local == nil && configuration.MySQL == nil && configuration.PostgreSQL == nil {
func ValidateStorage(config schema.StorageConfiguration, validator *schema.StructValidator) {
if config.Local == nil && config.MySQL == nil && config.PostgreSQL == nil {
validator.Push(errors.New(errStrStorage))
}
switch {
case configuration.MySQL != nil:
validateSQLConfiguration(&configuration.MySQL.SQLStorageConfiguration, validator, "mysql")
case configuration.PostgreSQL != nil:
validatePostgreSQLConfiguration(configuration.PostgreSQL, validator)
case configuration.Local != nil:
validateLocalStorageConfiguration(configuration.Local, validator)
case config.MySQL != nil:
validateSQLConfiguration(&config.MySQL.SQLStorageConfiguration, validator, "mysql")
case config.PostgreSQL != nil:
validatePostgreSQLConfiguration(config.PostgreSQL, validator)
case config.Local != nil:
validateLocalStorageConfiguration(config.Local, validator)
}
if configuration.EncryptionKey == "" {
if config.EncryptionKey == "" {
validator.Push(errors.New(errStrStorageEncryptionKeyMustBeProvided))
} else if len(configuration.EncryptionKey) < 20 {
} else if len(config.EncryptionKey) < 20 {
validator.Push(errors.New(errStrStorageEncryptionKeyTooShort))
}
}
func validateSQLConfiguration(configuration *schema.SQLStorageConfiguration, validator *schema.StructValidator, provider string) {
if configuration.Timeout == 0 {
configuration.Timeout = schema.DefaultSQLStorageConfiguration.Timeout
func validateSQLConfiguration(config *schema.SQLStorageConfiguration, validator *schema.StructValidator, provider string) {
if config.Timeout == 0 {
config.Timeout = schema.DefaultSQLStorageConfiguration.Timeout
}
if configuration.Host == "" {
if config.Host == "" {
validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, provider, "host"))
}
if configuration.Username == "" || configuration.Password == "" {
if config.Username == "" || config.Password == "" {
validator.Push(fmt.Errorf(errFmtStorageUserPassMustBeProvided, provider))
}
if configuration.Database == "" {
if config.Database == "" {
validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, provider, "database"))
}
}
func validatePostgreSQLConfiguration(configuration *schema.PostgreSQLStorageConfiguration, validator *schema.StructValidator) {
validateSQLConfiguration(&configuration.SQLStorageConfiguration, validator, "postgres")
func validatePostgreSQLConfiguration(config *schema.PostgreSQLStorageConfiguration, validator *schema.StructValidator) {
validateSQLConfiguration(&config.SQLStorageConfiguration, validator, "postgres")
if configuration.Schema == "" {
configuration.Schema = schema.DefaultPostgreSQLStorageConfiguration.Schema
if config.Schema == "" {
config.Schema = schema.DefaultPostgreSQLStorageConfiguration.Schema
}
// Deprecated. TODO: Remove in v4.36.0.
if configuration.SSLMode != "" && configuration.SSL.Mode == "" {
configuration.SSL.Mode = configuration.SSLMode
if config.SSLMode != "" && config.SSL.Mode == "" {
config.SSL.Mode = config.SSLMode
}
if configuration.SSL.Mode == "" {
configuration.SSL.Mode = schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode
} else if !utils.IsStringInSlice(configuration.SSL.Mode, storagePostgreSQLValidSSLModes) {
validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, configuration.SSL.Mode, strings.Join(storagePostgreSQLValidSSLModes, "', '")))
if config.SSL.Mode == "" {
config.SSL.Mode = schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode
} else if !utils.IsStringInSlice(config.SSL.Mode, validStoragePostgreSQLSSLModes) {
validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, strings.Join(validStoragePostgreSQLSSLModes, "', '"), config.SSL.Mode))
}
}
func validateLocalStorageConfiguration(configuration *schema.LocalStorageConfiguration, validator *schema.StructValidator) {
if configuration.Path == "" {
func validateLocalStorageConfiguration(config *schema.LocalStorageConfiguration, validator *schema.StructValidator) {
if config.Path == "" {
validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, "local", "path"))
}
}

View File

@ -10,24 +10,24 @@ import (
type StorageSuite struct {
suite.Suite
configuration schema.StorageConfiguration
validator *schema.StructValidator
config schema.StorageConfiguration
validator *schema.StructValidator
}
func (suite *StorageSuite) SetupTest() {
suite.validator = schema.NewStructValidator()
suite.configuration.EncryptionKey = testEncryptionKey
suite.configuration.Local = nil
suite.configuration.PostgreSQL = nil
suite.configuration.MySQL = nil
suite.config.EncryptionKey = testEncryptionKey
suite.config.Local = nil
suite.config.PostgreSQL = nil
suite.config.MySQL = nil
}
func (suite *StorageSuite) TestShouldValidateOneStorageIsConfigured() {
suite.configuration.Local = nil
suite.configuration.PostgreSQL = nil
suite.configuration.MySQL = nil
suite.config.Local = nil
suite.config.PostgreSQL = nil
suite.config.MySQL = nil
ValidateStorage(suite.configuration, suite.validator)
ValidateStorage(suite.config, suite.validator)
suite.Require().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
@ -35,37 +35,37 @@ func (suite *StorageSuite) TestShouldValidateOneStorageIsConfigured() {
}
func (suite *StorageSuite) TestShouldValidateLocalPathIsProvided() {
suite.configuration.Local = &schema.LocalStorageConfiguration{
suite.config.Local = &schema.LocalStorageConfiguration{
Path: "",
}
ValidateStorage(suite.configuration, suite.validator)
ValidateStorage(suite.config, suite.validator)
suite.Require().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: local: 'path' configuration option must be provided")
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: local: option 'path' is required")
suite.validator.Clear()
suite.configuration.Local.Path = "/myapth"
suite.config.Local.Path = "/myapth"
ValidateStorage(suite.configuration, suite.validator)
ValidateStorage(suite.config, suite.validator)
suite.Require().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 0)
}
func (suite *StorageSuite) TestShouldValidateMySQLHostUsernamePasswordAndDatabaseAreProvided() {
suite.configuration.MySQL = &schema.MySQLStorageConfiguration{}
ValidateStorage(suite.configuration, suite.validator)
suite.config.MySQL = &schema.MySQLStorageConfiguration{}
ValidateStorage(suite.config, suite.validator)
suite.Require().Len(suite.validator.Errors(), 3)
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: mysql: 'host' configuration option must be provided")
suite.Assert().EqualError(suite.validator.Errors()[1], "storage: mysql: 'username' and 'password' configuration options must be provided")
suite.Assert().EqualError(suite.validator.Errors()[2], "storage: mysql: 'database' configuration option must be provided")
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: mysql: option 'host' is required")
suite.Assert().EqualError(suite.validator.Errors()[1], "storage: mysql: option 'username' and 'password' are required")
suite.Assert().EqualError(suite.validator.Errors()[2], "storage: mysql: option 'database' is required")
suite.validator.Clear()
suite.configuration.MySQL = &schema.MySQLStorageConfiguration{
suite.config.MySQL = &schema.MySQLStorageConfiguration{
SQLStorageConfiguration: schema.SQLStorageConfiguration{
Host: "localhost",
Username: "myuser",
@ -73,24 +73,24 @@ func (suite *StorageSuite) TestShouldValidateMySQLHostUsernamePasswordAndDatabas
Database: "database",
},
}
ValidateStorage(suite.configuration, suite.validator)
ValidateStorage(suite.config, suite.validator)
suite.Require().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 0)
}
func (suite *StorageSuite) TestShouldValidatePostgreSQLHostUsernamePasswordAndDatabaseAreProvided() {
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{}
suite.configuration.MySQL = nil
ValidateStorage(suite.configuration, suite.validator)
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{}
suite.config.MySQL = nil
ValidateStorage(suite.config, suite.validator)
suite.Require().Len(suite.validator.Errors(), 3)
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: 'host' configuration option must be provided")
suite.Assert().EqualError(suite.validator.Errors()[1], "storage: postgres: 'username' and 'password' configuration options must be provided")
suite.Assert().EqualError(suite.validator.Errors()[2], "storage: postgres: 'database' configuration option must be provided")
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: option 'host' is required")
suite.Assert().EqualError(suite.validator.Errors()[1], "storage: postgres: option 'username' and 'password' are required")
suite.Assert().EqualError(suite.validator.Errors()[2], "storage: postgres: option 'database' is required")
suite.validator.Clear()
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
SQLStorageConfiguration: schema.SQLStorageConfiguration{
Host: "postgre",
Username: "myuser",
@ -98,14 +98,14 @@ func (suite *StorageSuite) TestShouldValidatePostgreSQLHostUsernamePasswordAndDa
Database: "database",
},
}
ValidateStorage(suite.configuration, suite.validator)
ValidateStorage(suite.config, suite.validator)
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().Len(suite.validator.Errors(), 0)
}
func (suite *StorageSuite) TestShouldValidatePostgresSSLModeAndSchemaDefaults() {
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
SQLStorageConfiguration: schema.SQLStorageConfiguration{
Host: "db1",
Username: "myuser",
@ -114,17 +114,17 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeAndSchemaDefaults()
},
}
ValidateStorage(suite.configuration, suite.validator)
ValidateStorage(suite.config, suite.validator)
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().Len(suite.validator.Errors(), 0)
suite.Assert().Equal("disable", suite.configuration.PostgreSQL.SSL.Mode)
suite.Assert().Equal("public", suite.configuration.PostgreSQL.Schema)
suite.Assert().Equal("disable", suite.config.PostgreSQL.SSL.Mode)
suite.Assert().Equal("public", suite.config.PostgreSQL.Schema)
}
func (suite *StorageSuite) TestShouldValidatePostgresDefaultsDontOverrideConfiguration() {
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
SQLStorageConfiguration: schema.SQLStorageConfiguration{
Host: "db1",
Username: "myuser",
@ -137,17 +137,17 @@ func (suite *StorageSuite) TestShouldValidatePostgresDefaultsDontOverrideConfigu
},
}
ValidateStorage(suite.configuration, suite.validator)
ValidateStorage(suite.config, suite.validator)
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().Len(suite.validator.Errors(), 0)
suite.Assert().Equal("require", suite.configuration.PostgreSQL.SSL.Mode)
suite.Assert().Equal("authelia", suite.configuration.PostgreSQL.Schema)
suite.Assert().Equal("require", suite.config.PostgreSQL.SSL.Mode)
suite.Assert().Equal("authelia", suite.config.PostgreSQL.Schema)
}
func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
SQLStorageConfiguration: schema.SQLStorageConfiguration{
Host: "db2",
Username: "myuser",
@ -159,16 +159,16 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
},
}
ValidateStorage(suite.configuration, suite.validator)
ValidateStorage(suite.config, suite.validator)
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: ssl: 'mode' configuration option 'unknown' is invalid: must be one of 'disable', 'require', 'verify-ca', 'verify-full'")
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: ssl: option 'mode' must be one of 'disable', 'require', 'verify-ca', 'verify-full' but it is configured as 'unknown'")
}
// Deprecated. TODO: Remove in v4.36.0.
func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeMappedForDeprecations() {
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
SQLStorageConfiguration: schema.SQLStorageConfiguration{
Host: "pg",
Username: "myuser",
@ -178,38 +178,38 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeMappedForDepre
SSLMode: "require",
}
ValidateStorage(suite.configuration, suite.validator)
ValidateStorage(suite.config, suite.validator)
suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().Len(suite.validator.Errors(), 0)
suite.Assert().Equal(suite.configuration.PostgreSQL.SSL.Mode, "require")
suite.Assert().Equal(suite.config.PostgreSQL.SSL.Mode, "require")
}
func (suite *StorageSuite) TestShouldRaiseErrorOnNoEncryptionKey() {
suite.configuration.EncryptionKey = ""
suite.configuration.Local = &schema.LocalStorageConfiguration{
suite.config.EncryptionKey = ""
suite.config.Local = &schema.LocalStorageConfiguration{
Path: "/this/is/a/path",
}
ValidateStorage(suite.configuration, suite.validator)
ValidateStorage(suite.config, suite.validator)
suite.Require().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: 'encryption_key' configuration option must be provided")
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: option 'encryption_key' must is required")
}
func (suite *StorageSuite) TestShouldRaiseErrorOnShortEncryptionKey() {
suite.configuration.EncryptionKey = "abc"
suite.configuration.Local = &schema.LocalStorageConfiguration{
suite.config.EncryptionKey = "abc"
suite.config.Local = &schema.LocalStorageConfiguration{
Path: "/this/is/a/path",
}
ValidateStorage(suite.configuration, suite.validator)
ValidateStorage(suite.config, suite.validator)
suite.Require().Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: 'encryption_key' configuration option must be 20 characters or longer")
suite.Assert().EqualError(suite.validator.Errors()[0], "storage: option 'encryption_key' must be 20 characters or longer")
}
func TestShouldRunStorageSuite(t *testing.T) {

View File

@ -2,20 +2,19 @@ package validator
import (
"fmt"
"regexp"
"strings"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
)
// ValidateTheme validates and update Theme configuration.
func ValidateTheme(configuration *schema.Configuration, validator *schema.StructValidator) {
if configuration.Theme == "" {
configuration.Theme = "light"
func ValidateTheme(config *schema.Configuration, validator *schema.StructValidator) {
if config.Theme == "" {
config.Theme = "light"
}
validThemes := regexp.MustCompile("light|dark|grey|auto")
if !validThemes.MatchString(configuration.Theme) {
validator.Push(fmt.Errorf("Theme: %s is not valid, valid themes are: \"light\", \"dark\", \"grey\" or \"auto\"", configuration.Theme))
if !utils.IsStringInSlice(config.Theme, validThemeNames) {
validator.Push(fmt.Errorf(errFmtThemeName, strings.Join(validThemeNames, "', '"), config.Theme))
}
}

View File

@ -10,33 +10,33 @@ import (
type Theme struct {
suite.Suite
configuration *schema.Configuration
validator *schema.StructValidator
config *schema.Configuration
validator *schema.StructValidator
}
func (suite *Theme) SetupTest() {
suite.validator = schema.NewStructValidator()
suite.configuration = &schema.Configuration{
suite.config = &schema.Configuration{
Theme: "light",
}
}
func (suite *Theme) TestShouldValidateCompleteConfiguration() {
ValidateTheme(suite.configuration, suite.validator)
ValidateTheme(suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
}
func (suite *Theme) TestShouldRaiseErrorWhenInvalidThemeProvided() {
suite.configuration.Theme = "invalid"
suite.config.Theme = "invalid"
ValidateTheme(suite.configuration, suite.validator)
ValidateTheme(suite.config, suite.validator)
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Theme: invalid is not valid, valid themes are: \"light\", \"dark\", \"grey\" or \"auto\"")
suite.Assert().EqualError(suite.validator.Errors()[0], "option 'theme' must be one of 'light', 'dark', 'grey', 'auto' but it is configured as 'invalid'")
}
func TestThemes(t *testing.T) {

View File

@ -9,40 +9,40 @@ import (
)
// ValidateTOTP validates and update TOTP configuration.
func ValidateTOTP(configuration *schema.Configuration, validator *schema.StructValidator) {
if configuration.TOTP == nil {
configuration.TOTP = &schema.DefaultTOTPConfiguration
func ValidateTOTP(config *schema.Configuration, validator *schema.StructValidator) {
if config.TOTP == nil {
config.TOTP = &schema.DefaultTOTPConfiguration
return
}
if configuration.TOTP.Issuer == "" {
configuration.TOTP.Issuer = schema.DefaultTOTPConfiguration.Issuer
if config.TOTP.Issuer == "" {
config.TOTP.Issuer = schema.DefaultTOTPConfiguration.Issuer
}
if configuration.TOTP.Algorithm == "" {
configuration.TOTP.Algorithm = schema.DefaultTOTPConfiguration.Algorithm
if config.TOTP.Algorithm == "" {
config.TOTP.Algorithm = schema.DefaultTOTPConfiguration.Algorithm
} else {
configuration.TOTP.Algorithm = strings.ToUpper(configuration.TOTP.Algorithm)
config.TOTP.Algorithm = strings.ToUpper(config.TOTP.Algorithm)
if !utils.IsStringInSlice(configuration.TOTP.Algorithm, schema.TOTPPossibleAlgorithms) {
validator.Push(fmt.Errorf(errFmtTOTPInvalidAlgorithm, configuration.TOTP.Algorithm, strings.Join(schema.TOTPPossibleAlgorithms, ", ")))
if !utils.IsStringInSlice(config.TOTP.Algorithm, schema.TOTPPossibleAlgorithms) {
validator.Push(fmt.Errorf(errFmtTOTPInvalidAlgorithm, strings.Join(schema.TOTPPossibleAlgorithms, "', '"), config.TOTP.Algorithm))
}
}
if configuration.TOTP.Period == 0 {
configuration.TOTP.Period = schema.DefaultTOTPConfiguration.Period
} else if configuration.TOTP.Period < 15 {
validator.Push(fmt.Errorf(errFmtTOTPInvalidPeriod, configuration.TOTP.Period))
if config.TOTP.Period == 0 {
config.TOTP.Period = schema.DefaultTOTPConfiguration.Period
} else if config.TOTP.Period < 15 {
validator.Push(fmt.Errorf(errFmtTOTPInvalidPeriod, config.TOTP.Period))
}
if configuration.TOTP.Digits == 0 {
configuration.TOTP.Digits = schema.DefaultTOTPConfiguration.Digits
} else if configuration.TOTP.Digits != 6 && configuration.TOTP.Digits != 8 {
validator.Push(fmt.Errorf(errFmtTOTPInvalidDigits, configuration.TOTP.Digits))
if config.TOTP.Digits == 0 {
config.TOTP.Digits = schema.DefaultTOTPConfiguration.Digits
} else if config.TOTP.Digits != 6 && config.TOTP.Digits != 8 {
validator.Push(fmt.Errorf(errFmtTOTPInvalidDigits, config.TOTP.Digits))
}
if configuration.TOTP.Skew == nil {
configuration.TOTP.Skew = schema.DefaultTOTPConfiguration.Skew
if config.TOTP.Skew == nil {
config.TOTP.Skew = schema.DefaultTOTPConfiguration.Skew
}
}

View File

@ -53,7 +53,7 @@ func TestShouldRaiseErrorWhenInvalidTOTPAlgorithm(t *testing.T) {
ValidateTOTP(config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtTOTPInvalidAlgorithm, "SHA3", strings.Join(schema.TOTPPossibleAlgorithms, ", ")))
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtTOTPInvalidAlgorithm, strings.Join(schema.TOTPPossibleAlgorithms, "', '"), "SHA3"))
}
func TestShouldRaiseErrorWhenInvalidTOTPValues(t *testing.T) {

View File

@ -10,16 +10,19 @@ import (
)
func TestShouldCheckNTP(t *testing.T) {
config := schema.NTPConfiguration{
Address: "time.cloudflare.com:123",
Version: 4,
MaximumDesync: "3s",
DisableStartupCheck: false,
config := &schema.Configuration{
NTP: &schema.NTPConfiguration{
Address: "time.cloudflare.com:123",
Version: 4,
MaximumDesync: "3s",
DisableStartupCheck: false,
},
}
sv := schema.NewStructValidator()
validator.ValidateNTP(&config, sv)
ntp := NewProvider(&config)
sv := schema.NewStructValidator()
validator.ValidateNTP(config, sv)
ntp := NewProvider(config.NTP)
assert.NoError(t, ntp.StartupCheck())
}

View File

@ -41,6 +41,21 @@ access_control:
policy: two_factor
- domain: "singlefactor.example.com"
policy: one_factor
- domain: "resources.example.com"
policy: one_factor
resources: ["^/resources"]
- domain: "method.example.com"
policy: one_factor
methods: ["POST"]
- domain: "network.example.com"
policy: one_factor
networks: ["192.168.1.0/24"]
- domain: "group.example.com"
policy: one_factor
subject: ["group:basic"]
- domain: "user.example.com"
policy: one_factor
subject: ["user:john"]
notifier:
filesystem:

View File

@ -66,15 +66,15 @@ func (s *CLISuite) TestShouldPrintVersion() {
}
func (s *CLISuite) TestShouldValidateConfig() {
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "validate-config", "/config/configuration.yml"})
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "validate-config", "--config", "/config/configuration.yml"})
s.Assert().NoError(err)
s.Assert().Contains(output, "Configuration parsed successfully without errors")
s.Assert().Contains(output, "Configuration parsed and loaded successfully without errors.")
}
func (s *CLISuite) TestShouldFailValidateConfig() {
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "validate-config", "/config/invalid.yml"})
s.Assert().NotNil(err)
s.Assert().Contains(output, "Error Loading Configuration: stat /config/invalid.yml: no such file or directory")
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "validate-config", "--config", "/config/invalid.yml"})
s.Assert().NoError(err)
s.Assert().Contains(output, "failed to load configuration from yaml file(/config/invalid.yml) source: open /config/invalid.yml: no such file or directory")
}
func (s *CLISuite) TestShouldHashPasswordArgon2id() {
@ -168,12 +168,12 @@ func (s *CLISuite) TestStorageShouldShowErrWithoutConfig() {
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info"})
s.Assert().EqualError(err, "exit status 1")
s.Assert().Contains(output, "Error: storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided, storage: 'encryption_key' configuration option must be provided\n")
s.Assert().Contains(output, "Error: storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided, storage: option 'encryption_key' must is required\n")
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "history"})
s.Assert().EqualError(err, "exit status 1")
s.Assert().Contains(output, "Error: storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided, storage: 'encryption_key' configuration option must be provided\n")
s.Assert().Contains(output, "Error: storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided, storage: option 'encryption_key' must is required\n")
}
func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() {
@ -382,6 +382,94 @@ func (s *CLISuite) TestStorage05ShouldMigrateDown() {
s.Regexp(pattern1, output)
}
func (s *CLISuite) TestACLPolicyCheckVerbose() {
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://public.example.com", "--verbose", "--config", "/config/configuration.yml"})
s.Assert().NoError(err)
// This is an example of `authelia access-control check-policy --config .\internal\suites\CLI\configuration.yml --url=https://public.example.com --verbose`.
s.Contains(output, "Performing policy check for request to 'https://public.example.com' method 'GET'.\n\n")
s.Contains(output, " #\tDomain\tResource\tMethod\tNetwork\tSubject\n")
s.Contains(output, "* 1\thit\thit\t\thit\thit\thit\n")
s.Contains(output, " 2\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 3\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 4\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 5\tmiss\tmiss\t\thit\thit\thit\n")
s.Contains(output, " 6\tmiss\thit\t\tmiss\thit\thit\n")
s.Contains(output, " 7\tmiss\thit\t\thit\tmiss\thit\n")
s.Contains(output, " 8\tmiss\thit\t\thit\thit\tmay\n")
s.Contains(output, " 9\tmiss\thit\t\thit\thit\tmay\n")
s.Contains(output, "The policy 'bypass' from rule #1 will be applied to this request.")
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://admin.example.com", "--method=HEAD", "--username=tom", "--groups=basic,test", "--ip=192.168.2.3", "--verbose", "--config", "/config/configuration.yml"})
s.Assert().NoError(err)
// This is an example of `authelia access-control check-policy --config .\internal\suites\CLI\configuration.yml --url=https://admin.example.com --method=HEAD --username=tom --groups=basic,test --ip=192.168.2.3 --verbose`.
s.Contains(output, "Performing policy check for request to 'https://admin.example.com' method 'HEAD' username 'tom' groups 'basic,test' from IP '192.168.2.3'.\n\n")
s.Contains(output, " #\tDomain\tResource\tMethod\tNetwork\tSubject\n")
s.Contains(output, " #\tDomain\tResource\tMethod\tNetwork\tSubject\n")
s.Contains(output, " 1\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, "* 2\thit\thit\t\thit\thit\thit\n")
s.Contains(output, " 3\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 4\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 5\tmiss\tmiss\t\thit\thit\thit\n")
s.Contains(output, " 6\tmiss\thit\t\tmiss\thit\thit\n")
s.Contains(output, " 7\tmiss\thit\t\thit\tmiss\thit\n")
s.Contains(output, " 8\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 9\tmiss\thit\t\thit\thit\tmiss\n")
s.Contains(output, "The policy 'two_factor' from rule #2 will be applied to this request.")
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://resources.example.com/resources/test", "--method=POST", "--username=john", "--groups=admin,test", "--ip=192.168.1.3", "--verbose", "--config", "/config/configuration.yml"})
s.Assert().NoError(err)
// This is an example of `authelia access-control check-policy --config .\internal\suites\CLI\configuration.yml --url=https://resources.example.com/resources/test --method=POST --username=john --groups=admin,test --ip=192.168.1.3 --verbose`.
s.Contains(output, "Performing policy check for request to 'https://resources.example.com/resources/test' method 'POST' username 'john' groups 'admin,test' from IP '192.168.1.3'.\n\n")
s.Contains(output, " #\tDomain\tResource\tMethod\tNetwork\tSubject\n")
s.Contains(output, " 1\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 2\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 3\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 4\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, "* 5\thit\thit\t\thit\thit\thit\n")
s.Contains(output, " 6\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 7\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 8\tmiss\thit\t\thit\thit\tmiss\n")
s.Contains(output, " 9\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, "The policy 'one_factor' from rule #5 will be applied to this request.")
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://user.example.com/resources/test", "--method=HEAD", "--username=john", "--groups=admin,test", "--ip=192.168.1.3", "--verbose", "--config", "/config/configuration.yml"})
s.Assert().NoError(err)
// This is an example of `access-control check-policy --config .\internal\suites\CLI\configuration.yml --url=https://user.example.com --method=HEAD --username=john --groups=admin,test --ip=192.168.1.3 --verbose`.
s.Contains(output, "Performing policy check for request to 'https://user.example.com/resources/test' method 'HEAD' username 'john' groups 'admin,test' from IP '192.168.1.3'.\n\n")
s.Contains(output, " #\tDomain\tResource\tMethod\tNetwork\tSubject\n")
s.Contains(output, " 1\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 2\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 3\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 4\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 5\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 6\tmiss\thit\t\tmiss\thit\thit\n")
s.Contains(output, " 7\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 8\tmiss\thit\t\thit\thit\tmiss\n")
s.Contains(output, "* 9\thit\thit\t\thit\thit\thit\n")
s.Contains(output, "The policy 'one_factor' from rule #9 will be applied to this request.")
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://user.example.com", "--method=HEAD", "--ip=192.168.1.3", "--verbose", "--config", "/config/configuration.yml"})
s.Assert().NoError(err)
// This is an example of `authelia access-control check-policy --config .\internal\suites\CLI\configuration.yml --url=https://user.example.com --method=HEAD --ip=192.168.1.3 --verbose`.
s.Contains(output, "Performing policy check for request to 'https://user.example.com' method 'HEAD' from IP '192.168.1.3'.\n\n")
s.Contains(output, " #\tDomain\tResource\tMethod\tNetwork\tSubject\n")
s.Contains(output, " 1\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 2\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 3\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 4\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 5\tmiss\tmiss\t\thit\thit\thit\n")
s.Contains(output, " 6\tmiss\thit\t\tmiss\thit\thit\n")
s.Contains(output, " 7\tmiss\thit\t\thit\thit\thit\n")
s.Contains(output, " 8\tmiss\thit\t\thit\thit\tmay\n")
s.Contains(output, "~ 9\thit\thit\t\thit\thit\tmay\n")
s.Contains(output, "The policy 'one_factor' from rule #9 will potentially be applied to this request. Otherwise the policy 'bypass' from the default policy will be.")
}
func TestCLISuite(t *testing.T) {
if testing.Short() {
t.Skip("skipping suite test in short mode")

View File

@ -68,12 +68,12 @@ func TestShouldReturnZeroAndErrorOnInvalidTLSVersions(t *testing.T) {
version, err := TLSStringToTLSConfigVersion("TLS1.4")
assert.Error(t, err)
assert.Equal(t, uint16(0), version)
assert.EqualError(t, err, "supplied TLS version isn't supported")
assert.EqualError(t, err, "supplied tls version isn't supported")
version, err = TLSStringToTLSConfigVersion("SSL3.0")
assert.Error(t, err)
assert.Equal(t, uint16(0), version)
assert.EqualError(t, err, "supplied TLS version isn't supported")
assert.EqualError(t, err, "supplied tls version isn't supported")
}
func TestShouldReturnErrWhenX509DirectoryNotExist(t *testing.T) {

View File

@ -73,4 +73,4 @@ var htmlEscaper = strings.NewReplacer(
var ErrTimeoutReached = errors.New("timeout reached")
// ErrTLSVersionNotSupported returned when an unknown TLS version supplied.
var ErrTLSVersionNotSupported = errors.New("supplied TLS version isn't supported")
var ErrTLSVersionNotSupported = errors.New("supplied tls version isn't supported")

View File

@ -38,13 +38,13 @@ func ParseDurationString(input string) (time.Duration, error) {
case input == "0" || len(matches) == 3:
seconds, err := strconv.Atoi(input)
if err != nil {
return 0, fmt.Errorf("could not convert the input string of %s into a duration: %s", input, err)
return 0, fmt.Errorf("could not parse '%s' as a duration: %w", input, err)
}
duration = time.Duration(seconds) * time.Second
case input != "":
// Throw this error if input is anything other than a blank string, blank string will default to a duration of nothing.
return 0, fmt.Errorf("could not convert the input string of %s into a duration", input)
return 0, fmt.Errorf("could not parse '%s' as a duration", input)
}
return duration, nil

View File

@ -47,25 +47,25 @@ func TestShouldParseSecondsString(t *testing.T) {
func TestShouldNotParseDurationStringWithOutOfOrderQuantitiesAndUnits(t *testing.T) {
duration, err := ParseDurationString("h1")
assert.EqualError(t, err, "could not convert the input string of h1 into a duration")
assert.EqualError(t, err, "could not parse 'h1' as a duration")
assert.Equal(t, time.Duration(0), duration)
}
func TestShouldNotParseBadDurationString(t *testing.T) {
duration, err := ParseDurationString("10x")
assert.EqualError(t, err, "could not convert the input string of 10x into a duration")
assert.EqualError(t, err, "could not parse '10x' as a duration")
assert.Equal(t, time.Duration(0), duration)
}
func TestShouldNotParseDurationStringWithMultiValueUnits(t *testing.T) {
duration, err := ParseDurationString("10ms")
assert.EqualError(t, err, "could not convert the input string of 10ms into a duration")
assert.EqualError(t, err, "could not parse '10ms' as a duration")
assert.Equal(t, time.Duration(0), duration)
}
func TestShouldNotParseDurationStringWithLeadingZero(t *testing.T) {
duration, err := ParseDurationString("005h")
assert.EqualError(t, err, "could not convert the input string of 005h into a duration")
assert.EqualError(t, err, "could not parse '005h' as a duration")
assert.Equal(t, time.Duration(0), duration)
}