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
parent
d87a56fa1a
commit
3c81e75d79
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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):], " ")
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
for _, err = range val.Errors() {
|
||||
fmt.Printf("\t - %v\n", err)
|
||||
}
|
||||
|
||||
if len(errors) != 0 {
|
||||
logger.Error("Errors occurred while loading the configuration:")
|
||||
fmt.Println("")
|
||||
|
||||
for _, err := range errors {
|
||||
logger.Errorf(" %+v", err)
|
||||
if !val.HasWarnings() {
|
||||
break
|
||||
}
|
||||
|
||||
logger.Fatal("Can't continue due to errors")
|
||||
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)
|
||||
}
|
||||
|
||||
log.Println("Configuration parsed successfully without errors.")
|
||||
fmt.Println("")
|
||||
default:
|
||||
fmt.Println("Configuration parsed and loaded successfully without errors.")
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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, "', '")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,107 +12,117 @@ import (
|
|||
|
||||
type AccessControl struct {
|
||||
suite.Suite
|
||||
configuration schema.AccessControlConfiguration
|
||||
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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
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
|
||||
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
|
||||
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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
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"}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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'")
|
||||
}
|
|
@ -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, ", ")))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,34 +12,34 @@ import (
|
|||
|
||||
type NotifierSuite struct {
|
||||
suite.Suite
|
||||
configuration schema.NotifierConfiguration
|
||||
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())
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'")
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
if config.Redis != nil {
|
||||
if config.Redis.HighAvailability != nil {
|
||||
validateRedisSentinel(config, validator)
|
||||
} else {
|
||||
validator.Push(fmt.Errorf("Session provider redis is configured for high availability but doesn't have a sentinel_name which is required"))
|
||||
}
|
||||
} 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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,24 +10,24 @@ import (
|
|||
|
||||
type StorageSuite struct {
|
||||
suite.Suite
|
||||
configuration schema.StorageConfiguration
|
||||
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) {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,33 +10,33 @@ import (
|
|||
|
||||
type Theme struct {
|
||||
suite.Suite
|
||||
configuration *schema.Configuration
|
||||
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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -10,16 +10,19 @@ import (
|
|||
)
|
||||
|
||||
func TestShouldCheckNTP(t *testing.T) {
|
||||
config := schema.NTPConfiguration{
|
||||
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())
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue