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.
|
integrations, it only checks that your configuration syntax is valid.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ authelia validate-config configuration.yml
|
$ authelia validate-config --config configuration.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
# Duration Notation Format
|
# Duration Notation Format
|
||||||
|
|
|
@ -124,12 +124,23 @@ func isMatchForNetworks(subject Subject, acl *AccessControlRule) (match bool) {
|
||||||
return false
|
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) {
|
func isMatchForSubjects(subject Subject, acl *AccessControlRule) (match bool) {
|
||||||
// If there are no subjects in this rule then the subject condition is a match.
|
if subject.IsAnonymous() {
|
||||||
if len(acl.Subjects) == 0 || subject.IsAnonymous() {
|
|
||||||
return true
|
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).
|
// Iterate over the subjects until we find a match (return true) or until we exit the loop (return false).
|
||||||
for _, subjectRule := range acl.Subjects {
|
for _, subjectRule := range acl.Subjects {
|
||||||
if subjectRule.IsMatch(subject) {
|
if subjectRule.IsMatch(subject) {
|
||||||
|
|
|
@ -66,3 +66,28 @@ func (p Authorizer) GetRequiredLevel(subject Subject, object Object) Level {
|
||||||
|
|
||||||
return p.defaultPolicy
|
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) {
|
func (s *AuthorizerTester) CheckAuthorizations(t *testing.T, subject Subject, requestURI, method string, expectedLevel Level) {
|
||||||
url, _ := url.ParseRequestURI(requestURI)
|
targetURL, _ := url.ParseRequestURI(requestURI)
|
||||||
|
|
||||||
object := Object{
|
object := NewObject(targetURL, method)
|
||||||
Scheme: url.Scheme,
|
|
||||||
Domain: url.Hostname(),
|
|
||||||
Path: url.Path,
|
|
||||||
Method: method,
|
|
||||||
}
|
|
||||||
|
|
||||||
level := s.GetRequiredLevel(subject, object)
|
level := s.GetRequiredLevel(subject, object)
|
||||||
|
|
||||||
assert.Equal(t, expectedLevel, level)
|
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 {
|
type AuthorizerTesterBuilder struct {
|
||||||
config schema.AccessControlConfiguration
|
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(), John, "https://private.example.com", "GET", TwoFactor)
|
||||||
tester.CheckAuthorizations(s.T(), Bob, "https://private.example.com", "GET", Denied)
|
tester.CheckAuthorizations(s.T(), Bob, "https://private.example.com", "GET", Denied)
|
||||||
tester.CheckAuthorizations(s.T(), AnonymousUser, "https://private.example.com", "GET", TwoFactor)
|
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() {
|
func (s *AuthorizerSuite) TestPolicyToLevel() {
|
||||||
|
|
|
@ -58,3 +58,27 @@ func NewObject(targetURL *url.URL, method string) (object Object) {
|
||||||
|
|
||||||
return 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
|
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) {
|
func schemaSubjectToACLSubject(subjectRule string) (subject AccessControlSubject) {
|
||||||
if strings.HasPrefix(subjectRule, userPrefix) {
|
if strings.HasPrefix(subjectRule, userPrefix) {
|
||||||
user := strings.Trim(subjectRule[len(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 (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration"
|
"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.
|
// cmdWithConfigFlags is used for commands which require access to the configuration to add the flag to the command.
|
||||||
func cmdWithConfigFlags(cmd *cobra.Command) {
|
func cmdWithConfigFlags(cmd *cobra.Command, persistent bool, configs []string) {
|
||||||
cmd.Flags().StringSliceP("config", "c", []string{}, "Configuration files")
|
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
|
var config *schema.Configuration
|
||||||
|
|
||||||
func newCmdWithConfigPreRun(ensureConfigExists, validateKeys, validateConfiguration bool) func(cmd *cobra.Command, args []string) {
|
func newCmdWithConfigPreRun(ensureConfigExists, validateKeys, validateConfiguration bool) func(cmd *cobra.Command, args []string) {
|
||||||
return func(cmd *cobra.Command, _ []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")
|
logger = logging.Logger()
|
||||||
if err != nil {
|
|
||||||
|
if configs, err = cmd.Flags().GetStringSlice("config"); err != nil {
|
||||||
logger.Fatalf("Error reading flags: %v", err)
|
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()
|
config, val, err = loadConfig(configs, validateKeys, validateConfiguration)
|
||||||
|
|
||||||
keys, config, err = configuration.Load(val, configuration.NewDefaultSources(configs, configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("Error occurred loading configuration: %v", err)
|
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()
|
warnings := val.Warnings()
|
||||||
if len(warnings) != 0 {
|
if len(warnings) != 0 {
|
||||||
for _, warning := range warnings {
|
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.
|
# 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 (
|
const (
|
||||||
storageMigrateDirectionUp = "up"
|
storageMigrateDirectionUp = "up"
|
||||||
storageMigrateDirectionDown = "down"
|
storageMigrateDirectionDown = "down"
|
||||||
|
|
|
@ -31,7 +31,7 @@ func NewRootCmd() (cmd *cobra.Command) {
|
||||||
Run: cmdRootRun,
|
Run: cmdRootRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdWithConfigFlags(cmd)
|
cmdWithConfigFlags(cmd, false, []string{})
|
||||||
|
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
newBuildInfoCmd(),
|
newBuildInfoCmd(),
|
||||||
|
@ -41,6 +41,7 @@ func NewRootCmd() (cmd *cobra.Command) {
|
||||||
NewRSACmd(),
|
NewRSACmd(),
|
||||||
NewStorageCmd(),
|
NewStorageCmd(),
|
||||||
newValidateConfigCmd(),
|
newValidateConfigCmd(),
|
||||||
|
newAccessControlCommand(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
|
@ -13,7 +13,7 @@ func NewStorageCmd() (cmd *cobra.Command) {
|
||||||
PersistentPreRunE: storagePersistentPreRunE,
|
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")
|
cmd.PersistentFlags().String("encryption-key", "", "the storage encryption key to use")
|
||||||
|
|
||||||
|
|
|
@ -1,66 +1,70 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"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/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/validator"
|
|
||||||
"github.com/authelia/authelia/v4/internal/logging"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newValidateConfigCmd() (cmd *cobra.Command) {
|
func newValidateConfigCmd() (cmd *cobra.Command) {
|
||||||
cmd = &cobra.Command{
|
cmd = &cobra.Command{
|
||||||
Use: "validate-config [yaml]",
|
Use: "validate-config",
|
||||||
Short: "Check a configuration against the internal configuration validation mechanisms",
|
Short: "Check a configuration against the internal configuration validation mechanisms",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.NoArgs,
|
||||||
Run: cmdValidateConfigRun,
|
RunE: cmdValidateConfigRunE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmdWithConfigFlags(cmd, false, []string{"config.yml"})
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdValidateConfigRun(_ *cobra.Command, args []string) {
|
func cmdValidateConfigRunE(cmd *cobra.Command, _ []string) (err error) {
|
||||||
logger := logging.Logger()
|
var (
|
||||||
|
configs []string
|
||||||
|
val *schema.StructValidator
|
||||||
|
)
|
||||||
|
|
||||||
configPath := args[0]
|
if configs, err = cmd.Flags().GetStringSlice("config"); err != nil {
|
||||||
if _, err := os.Stat(configPath); err != nil {
|
return err
|
||||||
logger.Fatalf("Error Loading Configuration: %v\n", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val := schema.NewStructValidator()
|
config, val, err = loadConfig(configs, true, true)
|
||||||
|
|
||||||
keys, conf, err := configuration.Load(val, configuration.NewYAMLFileSource(configPath))
|
|
||||||
if err != nil {
|
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)
|
switch {
|
||||||
validator.ValidateConfiguration(conf, val)
|
case val.HasErrors():
|
||||||
|
fmt.Println("Configuration parsed and loaded with errors:")
|
||||||
|
fmt.Println("")
|
||||||
|
|
||||||
warnings := val.Warnings()
|
for _, err = range val.Errors() {
|
||||||
errors := val.Errors()
|
fmt.Printf("\t - %v\n", err)
|
||||||
|
|
||||||
if len(warnings) != 0 {
|
|
||||||
logger.Warn("Warnings occurred while loading the configuration:")
|
|
||||||
|
|
||||||
for _, warn := range warnings {
|
|
||||||
logger.Warnf(" %+v", warn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errors) != 0 {
|
|
||||||
logger.Error("Errors occurred while loading the configuration:")
|
|
||||||
|
|
||||||
for _, err := range errors {
|
|
||||||
logger.Errorf(" %+v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Fatal("Can't continue due to errors")
|
fmt.Println("")
|
||||||
|
|
||||||
|
if !val.HasWarnings() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fallthrough
|
||||||
|
case val.HasWarnings():
|
||||||
|
fmt.Println("Configuration parsed and loaded with warnings:")
|
||||||
|
fmt.Println("")
|
||||||
|
|
||||||
|
for _, err = range val.Warnings() {
|
||||||
|
fmt.Printf("\t - %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("")
|
||||||
|
default:
|
||||||
|
fmt.Println("Configuration parsed and loaded successfully without errors.")
|
||||||
|
fmt.Println("")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Configuration parsed successfully without errors.")
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -260,7 +260,7 @@ func TestShouldHandleErrInvalidatorWhenSMTPSenderBlank(t *testing.T) {
|
||||||
require.Len(t, val.Errors(), 1)
|
require.Len(t, val.Errors(), 1)
|
||||||
assert.Len(t, val.Warnings(), 0)
|
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) {
|
func TestShouldDecodeSMTPSenderWithoutName(t *testing.T) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
|
|
||||||
// IsPolicyValid check if policy is valid.
|
// IsPolicyValid check if policy is valid.
|
||||||
func IsPolicyValid(policy string) (isValid bool) {
|
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.
|
// 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.
|
// IsNetworkGroupValid check if a network group is valid.
|
||||||
func IsNetworkGroupValid(configuration schema.AccessControlConfiguration, network string) bool {
|
func IsNetworkGroupValid(config schema.AccessControlConfiguration, network string) bool {
|
||||||
for _, networks := range configuration.Networks {
|
for _, networks := range config.Networks {
|
||||||
if network != networks.Name {
|
if network != networks.Name {
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
|
@ -49,21 +49,29 @@ func IsNetworkValid(network string) (isValid bool) {
|
||||||
return true
|
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.
|
// ValidateAccessControl validates access control configuration.
|
||||||
func ValidateAccessControl(configuration *schema.AccessControlConfiguration, validator *schema.StructValidator) {
|
func ValidateAccessControl(config *schema.Configuration, validator *schema.StructValidator) {
|
||||||
if configuration.DefaultPolicy == "" {
|
if config.AccessControl.DefaultPolicy == "" {
|
||||||
configuration.DefaultPolicy = policyDeny
|
config.AccessControl.DefaultPolicy = policyDeny
|
||||||
}
|
}
|
||||||
|
|
||||||
if !IsPolicyValid(configuration.DefaultPolicy) {
|
if !IsPolicyValid(config.AccessControl.DefaultPolicy) {
|
||||||
validator.Push(fmt.Errorf("'default_policy' must either be 'deny', 'two_factor', 'one_factor' or 'bypass'"))
|
validator.Push(fmt.Errorf(errFmtAccessControlDefaultPolicyValue, strings.Join(validACLRulePolicies, "', '"), config.AccessControl.DefaultPolicy))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Networks != nil {
|
if config.AccessControl.Networks != nil {
|
||||||
for _, n := range configuration.Networks {
|
for _, n := range config.AccessControl.Networks {
|
||||||
for _, networks := range n.Networks {
|
for _, networks := range n.Networks {
|
||||||
if !IsNetworkValid(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.
|
// ValidateRules validates an ACL Rule configuration.
|
||||||
func ValidateRules(configuration schema.AccessControlConfiguration, validator *schema.StructValidator) {
|
func ValidateRules(config *schema.Configuration, validator *schema.StructValidator) {
|
||||||
if configuration.Rules == nil || len(configuration.Rules) == 0 {
|
if config.AccessControl.Rules == nil || len(config.AccessControl.Rules) == 0 {
|
||||||
if configuration.DefaultPolicy != policyOneFactor && configuration.DefaultPolicy != policyTwoFactor {
|
if config.AccessControl.DefaultPolicy != policyOneFactor && config.AccessControl.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))
|
validator.Push(fmt.Errorf(errFmtAccessControlDefaultPolicyWithoutRules, config.AccessControl.DefaultPolicy))
|
||||||
|
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, rule := range configuration.Rules {
|
for i, rule := range config.AccessControl.Rules {
|
||||||
rulePosition := i + 1
|
rulePosition := i + 1
|
||||||
|
|
||||||
if len(rule.Domains) == 0 {
|
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) {
|
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)
|
validateResources(rulePosition, rule, validator)
|
||||||
|
|
||||||
|
@ -104,16 +112,16 @@ func ValidateRules(configuration schema.AccessControlConfiguration, validator *s
|
||||||
validateMethods(rulePosition, rule, validator)
|
validateMethods(rulePosition, rule, validator)
|
||||||
|
|
||||||
if rule.Policy == policyBypass && len(rule.Subjects) != 0 {
|
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 {
|
for _, network := range rule.Networks {
|
||||||
if !IsNetworkValid(network) {
|
if !IsNetworkValid(network) {
|
||||||
if !IsNetworkGroupValid(configuration, network) {
|
if !IsNetworkGroupValid(config, 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))
|
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) {
|
func validateResources(rulePosition int, rule schema.ACLRule, validator *schema.StructValidator) {
|
||||||
for _, resource := range rule.Resources {
|
for _, resource := range rule.Resources {
|
||||||
if err := IsResourceValid(resource); err != nil {
|
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 _, subjectRule := range rule.Subjects {
|
||||||
for _, subject := range subjectRule {
|
for _, subject := range subjectRule {
|
||||||
if !IsSubjectValid(subject) {
|
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) {
|
func validateMethods(rulePosition int, rule schema.ACLRule, validator *schema.StructValidator) {
|
||||||
for _, method := range rule.Methods {
|
for _, method := range rule.Methods {
|
||||||
if !utils.IsStringInSliceFold(method, validHTTPRequestMethods) {
|
if !utils.IsStringInSliceFold(method, validACLRuleMethods) {
|
||||||
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, ", ")))
|
validator.Push(fmt.Errorf(errFmtAccessControlRuleMethodInvalid, ruleDescriptor(rulePosition, rule), method, strings.Join(validACLRuleMethods, "', '")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,107 +12,117 @@ import (
|
||||||
|
|
||||||
type AccessControl struct {
|
type AccessControl struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
configuration schema.AccessControlConfiguration
|
config *schema.Configuration
|
||||||
validator *schema.StructValidator
|
validator *schema.StructValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccessControl) SetupTest() {
|
func (suite *AccessControl) SetupTest() {
|
||||||
suite.validator = schema.NewStructValidator()
|
suite.validator = schema.NewStructValidator()
|
||||||
suite.configuration.DefaultPolicy = policyDeny
|
suite.config = &schema.Configuration{
|
||||||
suite.configuration.Networks = schema.DefaultACLNetwork
|
AccessControl: schema.AccessControlConfiguration{
|
||||||
suite.configuration.Rules = schema.DefaultACLRule
|
DefaultPolicy: policyDeny,
|
||||||
|
|
||||||
|
Networks: schema.DefaultACLNetwork,
|
||||||
|
Rules: schema.DefaultACLRule,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccessControl) TestShouldValidateCompleteConfiguration() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *AccessControl) TestShouldRaiseErrorInvalidNetworkGroupNetwork() {
|
||||||
suite.configuration.Networks = []schema.ACLNetwork{
|
suite.config.AccessControl.Networks = []schema.ACLNetwork{
|
||||||
{
|
{
|
||||||
Name: "internal",
|
Name: "internal",
|
||||||
Networks: []string{"abc.def.ghi.jkl"},
|
Networks: []string{"abc.def.ghi.jkl"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateAccessControl(&suite.configuration, suite.validator)
|
ValidateAccessControl(suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasWarnings())
|
suite.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasErrors())
|
||||||
suite.Require().Len(suite.validator.Warnings(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 4)
|
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()[0], "access control: rule #1: rule is invalid: must have the option 'domain' configured")
|
||||||
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()[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], "Rule #2 is invalid, a policy must have one or more domains")
|
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], "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()[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() {
|
func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() {
|
||||||
suite.configuration.Rules = []schema.ACLRule{
|
suite.config.AccessControl.Rules = []schema.ACLRule{
|
||||||
{
|
{
|
||||||
Domains: []string{"public.example.com"},
|
Domains: []string{"public.example.com"},
|
||||||
Policy: testInvalidPolicy,
|
Policy: testInvalidPolicy,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateRules(suite.configuration, suite.validator)
|
ValidateRules(suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasWarnings())
|
suite.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *AccessControl) TestShouldRaiseErrorInvalidNetwork() {
|
||||||
suite.configuration.Rules = []schema.ACLRule{
|
suite.config.AccessControl.Rules = []schema.ACLRule{
|
||||||
{
|
{
|
||||||
Domains: []string{"public.example.com"},
|
Domains: []string{"public.example.com"},
|
||||||
Policy: "bypass",
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *AccessControl) TestShouldRaiseErrorInvalidMethod() {
|
||||||
suite.configuration.Rules = []schema.ACLRule{
|
suite.config.AccessControl.Rules = []schema.ACLRule{
|
||||||
{
|
{
|
||||||
Domains: []string{"public.example.com"},
|
Domains: []string{"public.example.com"},
|
||||||
Policy: "bypass",
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *AccessControl) TestShouldRaiseErrorInvalidResource() {
|
||||||
suite.configuration.Rules = []schema.ACLRule{
|
suite.config.AccessControl.Rules = []schema.ACLRule{
|
||||||
{
|
{
|
||||||
Domains: []string{"public.example.com"},
|
Domains: []string{"public.example.com"},
|
||||||
Policy: "bypass",
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *AccessControl) TestShouldRaiseErrorInvalidSubject() {
|
||||||
domains := []string{"public.example.com"}
|
domains := []string{"public.example.com"}
|
||||||
subjects := [][]string{{"invalid"}}
|
subjects := [][]string{{"invalid"}}
|
||||||
suite.configuration.Rules = []schema.ACLRule{
|
suite.config.AccessControl.Rules = []schema.ACLRule{
|
||||||
{
|
{
|
||||||
Domains: domains,
|
Domains: domains,
|
||||||
Policy: "bypass",
|
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.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 2)
|
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()[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(errAccessControlInvalidPolicyWithSubjects, 1, domains, subjects))
|
suite.Assert().EqualError(suite.validator.Errors()[1], fmt.Sprintf(errAccessControlRuleBypassPolicyInvalidWithSubjects, ruleDescriptor(1, suite.config.AccessControl.Rules[0])))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccessControl(t *testing.T) {
|
func TestAccessControl(t *testing.T) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package validator
|
package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -11,260 +10,254 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateAuthenticationBackend validates and updates the authentication backend configuration.
|
// ValidateAuthenticationBackend validates and updates the authentication backend configuration.
|
||||||
func ValidateAuthenticationBackend(configuration *schema.AuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
func ValidateAuthenticationBackend(config *schema.AuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.LDAP == nil && configuration.File == nil {
|
if config.LDAP == nil && config.File == nil {
|
||||||
validator.Push(errors.New("Please provide `ldap` or `file` object in `authentication_backend`"))
|
validator.Push(fmt.Errorf(errFmtAuthBackendNotConfigured))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.LDAP != nil && configuration.File != nil {
|
if config.LDAP != nil && config.File != nil {
|
||||||
validator.Push(errors.New("You cannot provide both `ldap` and `file` objects in `authentication_backend`"))
|
validator.Push(fmt.Errorf(errFmtAuthBackendMultipleConfigured))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.File != nil {
|
if config.File != nil {
|
||||||
validateFileAuthenticationBackend(configuration.File, validator)
|
validateFileAuthenticationBackend(config.File, validator)
|
||||||
} else if configuration.LDAP != nil {
|
} else if config.LDAP != nil {
|
||||||
validateLDAPAuthenticationBackend(configuration.LDAP, validator)
|
validateLDAPAuthenticationBackend(config.LDAP, validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.RefreshInterval == "" {
|
if config.RefreshInterval == "" {
|
||||||
configuration.RefreshInterval = schema.RefreshIntervalDefault
|
config.RefreshInterval = schema.RefreshIntervalDefault
|
||||||
} else {
|
} else {
|
||||||
_, err := utils.ParseDurationString(configuration.RefreshInterval)
|
_, err := utils.ParseDurationString(config.RefreshInterval)
|
||||||
if err != nil && configuration.RefreshInterval != schema.ProfileRefreshDisabled && configuration.RefreshInterval != schema.ProfileRefreshAlways {
|
if err != nil && config.RefreshInterval != schema.ProfileRefreshDisabled && config.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))
|
validator.Push(fmt.Errorf(errFmtAuthBackendRefreshInterval, config.RefreshInterval, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateFileAuthenticationBackend validates and updates the file authentication backend configuration.
|
// validateFileAuthenticationBackend validates and updates the file authentication backend configuration.
|
||||||
func validateFileAuthenticationBackend(configuration *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
func validateFileAuthenticationBackend(config *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.Path == "" {
|
if config.Path == "" {
|
||||||
validator.Push(errors.New("Please provide a `path` for the users database in `authentication_backend`"))
|
validator.Push(fmt.Errorf(errFmtFileAuthBackendPathNotConfigured))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Password == nil {
|
if config.Password == nil {
|
||||||
configuration.Password = &schema.DefaultPasswordConfiguration
|
config.Password = &schema.DefaultPasswordConfiguration
|
||||||
} else {
|
} else {
|
||||||
// Salt Length.
|
// Salt Length.
|
||||||
switch {
|
switch {
|
||||||
case configuration.Password.SaltLength == 0:
|
case config.Password.SaltLength == 0:
|
||||||
configuration.Password.SaltLength = schema.DefaultPasswordConfiguration.SaltLength
|
config.Password.SaltLength = schema.DefaultPasswordConfiguration.SaltLength
|
||||||
case configuration.Password.SaltLength < 8:
|
case config.Password.SaltLength < 8:
|
||||||
validator.Push(fmt.Errorf("The salt length must be 2 or more, you configured %d", configuration.Password.SaltLength))
|
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordSaltLength, config.Password.SaltLength))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch configuration.Password.Algorithm {
|
switch config.Password.Algorithm {
|
||||||
case "":
|
case "":
|
||||||
configuration.Password.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
|
config.Password.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
|
||||||
fallthrough
|
fallthrough
|
||||||
case hashArgon2id:
|
case hashArgon2id:
|
||||||
validateFileAuthenticationBackendArgon2id(configuration, validator)
|
validateFileAuthenticationBackendArgon2id(config, validator)
|
||||||
case hashSHA512:
|
case hashSHA512:
|
||||||
validateFileAuthenticationBackendSHA512(configuration)
|
validateFileAuthenticationBackendSHA512(config)
|
||||||
default:
|
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 {
|
if config.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))
|
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidIterations, config.Password.Iterations))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateFileAuthenticationBackendSHA512(configuration *schema.FileAuthenticationBackendConfiguration) {
|
func validateFileAuthenticationBackendSHA512(config *schema.FileAuthenticationBackendConfiguration) {
|
||||||
// Iterations (time).
|
// Iterations (time).
|
||||||
if configuration.Password.Iterations == 0 {
|
if config.Password.Iterations == 0 {
|
||||||
configuration.Password.Iterations = schema.DefaultPasswordSHA512Configuration.Iterations
|
config.Password.Iterations = schema.DefaultPasswordSHA512Configuration.Iterations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func validateFileAuthenticationBackendArgon2id(configuration *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
func validateFileAuthenticationBackendArgon2id(config *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
||||||
// Iterations (time).
|
// Iterations (time).
|
||||||
if configuration.Password.Iterations == 0 {
|
if config.Password.Iterations == 0 {
|
||||||
configuration.Password.Iterations = schema.DefaultPasswordConfiguration.Iterations
|
config.Password.Iterations = schema.DefaultPasswordConfiguration.Iterations
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parallelism.
|
// Parallelism.
|
||||||
if configuration.Password.Parallelism == 0 {
|
if config.Password.Parallelism == 0 {
|
||||||
configuration.Password.Parallelism = schema.DefaultPasswordConfiguration.Parallelism
|
config.Password.Parallelism = schema.DefaultPasswordConfiguration.Parallelism
|
||||||
} else if configuration.Password.Parallelism < 1 {
|
} else if config.Password.Parallelism < 1 {
|
||||||
validator.Push(fmt.Errorf("Parallelism for argon2id must be 1 or more, you configured %d", configuration.Password.Parallelism))
|
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidParallelism, config.Password.Parallelism))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Memory.
|
// Memory.
|
||||||
if configuration.Password.Memory == 0 {
|
if config.Password.Memory == 0 {
|
||||||
configuration.Password.Memory = schema.DefaultPasswordConfiguration.Memory
|
config.Password.Memory = schema.DefaultPasswordConfiguration.Memory
|
||||||
} else if configuration.Password.Memory < configuration.Password.Parallelism*8 {
|
} else if config.Password.Memory < config.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))
|
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidMemory, config.Password.Parallelism, config.Password.Parallelism*8, config.Password.Memory))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key Length.
|
// Key Length.
|
||||||
if configuration.Password.KeyLength == 0 {
|
if config.Password.KeyLength == 0 {
|
||||||
configuration.Password.KeyLength = schema.DefaultPasswordConfiguration.KeyLength
|
config.Password.KeyLength = schema.DefaultPasswordConfiguration.KeyLength
|
||||||
} else if configuration.Password.KeyLength < 16 {
|
} else if config.Password.KeyLength < 16 {
|
||||||
validator.Push(fmt.Errorf("Key length for argon2id must be 16, you configured %d", configuration.Password.KeyLength))
|
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidKeyLength, config.Password.KeyLength))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateLDAPAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
func validateLDAPAuthenticationBackend(config *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.Timeout == 0 {
|
if config.Timeout == 0 {
|
||||||
configuration.Timeout = schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout
|
config.Timeout = schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Implementation == "" {
|
if config.Implementation == "" {
|
||||||
configuration.Implementation = schema.DefaultLDAPAuthenticationBackendConfiguration.Implementation
|
config.Implementation = schema.DefaultLDAPAuthenticationBackendConfiguration.Implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.TLS == nil {
|
if config.TLS == nil {
|
||||||
configuration.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
|
config.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.TLS.MinimumVersion == "" {
|
if config.TLS.MinimumVersion == "" {
|
||||||
configuration.TLS.MinimumVersion = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion
|
config.TLS.MinimumVersion = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS.MinimumVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := utils.TLSStringToTLSConfigVersion(configuration.TLS.MinimumVersion); err != nil {
|
if _, err := utils.TLSStringToTLSConfigVersion(config.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))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendTLSMinVersion, config.TLS.MinimumVersion, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch configuration.Implementation {
|
switch config.Implementation {
|
||||||
case schema.LDAPImplementationCustom:
|
case schema.LDAPImplementationCustom:
|
||||||
setDefaultImplementationCustomLDAPAuthenticationBackend(configuration)
|
setDefaultImplementationCustomLDAPAuthenticationBackend(config)
|
||||||
case schema.LDAPImplementationActiveDirectory:
|
case schema.LDAPImplementationActiveDirectory:
|
||||||
setDefaultImplementationActiveDirectoryLDAPAuthenticationBackend(configuration)
|
setDefaultImplementationActiveDirectoryLDAPAuthenticationBackend(config)
|
||||||
default:
|
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}") {
|
if strings.Contains(config.UsersFilter, "{0}") {
|
||||||
validator.Push(fmt.Errorf("authentication backend ldap users filter must not contain removed placeholders" +
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "users_filter", "{0}", "{input}"))
|
||||||
", {0} has been replaced with {input}"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(configuration.GroupsFilter, "{0}") ||
|
if strings.Contains(config.GroupsFilter, "{0}") {
|
||||||
strings.Contains(configuration.GroupsFilter, "{1}") {
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{0}", "{input}"))
|
||||||
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 configuration.URL == "" {
|
if strings.Contains(config.GroupsFilter, "{1}") {
|
||||||
validator.Push(errors.New("Please provide a URL to the LDAP server"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{1}", "{username}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.URL == "" {
|
||||||
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "url"))
|
||||||
} else {
|
} else {
|
||||||
ldapURL, serverName := validateLDAPURL(configuration.URL, validator)
|
validateLDAPAuthenticationBackendURL(config, validator)
|
||||||
|
|
||||||
configuration.URL = ldapURL
|
|
||||||
|
|
||||||
if configuration.TLS.ServerName == "" {
|
|
||||||
configuration.TLS.ServerName = serverName
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validateLDAPRequiredParameters(configuration, validator)
|
validateLDAPRequiredParameters(config, validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper for test purposes to exclude the hostname from the return.
|
func validateLDAPAuthenticationBackendURL(config *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
||||||
func validateLDAPURLSimple(ldapURL string, validator *schema.StructValidator) (finalURL string) {
|
var (
|
||||||
finalURL, _ = validateLDAPURL(ldapURL, validator)
|
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) {
|
return
|
||||||
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 "", ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !(parsedURL.Scheme == schemeLDAP || parsedURL.Scheme == schemeLDAPS) {
|
if parsedURL.Scheme != schemeLDAP && parsedURL.Scheme != schemeLDAPS {
|
||||||
validator.Push(errors.New("Unknown scheme for ldap url, should be ldap:// or ldaps://"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendURLInvalidScheme, parsedURL.Scheme))
|
||||||
return "", ""
|
|
||||||
|
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).
|
// 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 == "" {
|
if config.User == "" {
|
||||||
validator.Push(errors.New("Please provide a user name to connect to the LDAP server"))
|
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).
|
// 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 == "" {
|
if config.Password == "" {
|
||||||
validator.Push(errors.New("Please provide a password to connect to the LDAP server"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "password"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.BaseDN == "" {
|
if config.BaseDN == "" {
|
||||||
validator.Push(errors.New("Please provide a base DN to connect to the LDAP server"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "base_dn"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.UsersFilter == "" {
|
if config.UsersFilter == "" {
|
||||||
validator.Push(errors.New("Please provide a users filter with `users_filter` attribute"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "users_filter"))
|
||||||
} else {
|
} else {
|
||||||
if !strings.HasPrefix(configuration.UsersFilter, "(") || !strings.HasSuffix(configuration.UsersFilter, ")") {
|
if !strings.HasPrefix(config.UsersFilter, "(") || !strings.HasSuffix(config.UsersFilter, ")") {
|
||||||
validator.Push(errors.New("The users filter should contain enclosing parenthesis. For instance {username_attribute}={input} should be ({username_attribute}={input})"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "users_filter", config.UsersFilter, config.UsersFilter))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(configuration.UsersFilter, "{username_attribute}") {
|
if !strings.Contains(config.UsersFilter, "{username_attribute}") {
|
||||||
validator.Push(errors.New("Unable to detect {username_attribute} placeholder in users_filter, your configuration is broken. " +
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, "users_filter", "username_attribute"))
|
||||||
"Please review configuration options listed at https://www.authelia.com/docs/configuration/authentication/ldap.html"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test helps the user know that users_filter is broken after the breaking change induced by this commit.
|
// 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}") {
|
if !strings.Contains(config.UsersFilter, "{input}") {
|
||||||
validator.Push(errors.New("Unable to detect {input} placeholder in users_filter, your configuration might be broken. " +
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, "users_filter", "input"))
|
||||||
"Please review configuration options listed at https://www.authelia.com/docs/configuration/authentication/ldap.html"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.GroupsFilter == "" {
|
if config.GroupsFilter == "" {
|
||||||
validator.Push(errors.New("Please provide a groups filter with `groups_filter` attribute"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "groups_filter"))
|
||||||
} else if !strings.HasPrefix(configuration.GroupsFilter, "(") || !strings.HasSuffix(configuration.GroupsFilter, ")") {
|
} else if !strings.HasPrefix(config.GroupsFilter, "(") || !strings.HasSuffix(config.GroupsFilter, ")") {
|
||||||
validator.Push(errors.New("The groups filter should contain enclosing parenthesis. For instance cn={input} should be (cn={input})"))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "groups_filter", config.GroupsFilter, config.GroupsFilter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setDefaultImplementationActiveDirectoryLDAPAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration) {
|
func setDefaultImplementationActiveDirectoryLDAPAuthenticationBackend(config *schema.LDAPAuthenticationBackendConfiguration) {
|
||||||
if configuration.UsersFilter == "" {
|
if config.UsersFilter == "" {
|
||||||
configuration.UsersFilter = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter
|
config.UsersFilter = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.UsernameAttribute == "" {
|
if config.UsernameAttribute == "" {
|
||||||
configuration.UsernameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute
|
config.UsernameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.DisplayNameAttribute == "" {
|
if config.DisplayNameAttribute == "" {
|
||||||
configuration.DisplayNameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute
|
config.DisplayNameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.MailAttribute == "" {
|
if config.MailAttribute == "" {
|
||||||
configuration.MailAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute
|
config.MailAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.GroupsFilter == "" {
|
if config.GroupsFilter == "" {
|
||||||
configuration.GroupsFilter = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter
|
config.GroupsFilter = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.GroupNameAttribute == "" {
|
if config.GroupNameAttribute == "" {
|
||||||
configuration.GroupNameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute
|
config.GroupNameAttribute = schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setDefaultImplementationCustomLDAPAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration) {
|
func setDefaultImplementationCustomLDAPAuthenticationBackend(config *schema.LDAPAuthenticationBackendConfiguration) {
|
||||||
if configuration.UsernameAttribute == "" {
|
if config.UsernameAttribute == "" {
|
||||||
configuration.UsernameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.UsernameAttribute
|
config.UsernameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.UsernameAttribute
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.GroupNameAttribute == "" {
|
if config.GroupNameAttribute == "" {
|
||||||
configuration.GroupNameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.GroupNameAttribute
|
config.GroupNameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.GroupNameAttribute
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.MailAttribute == "" {
|
if config.MailAttribute == "" {
|
||||||
configuration.MailAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.MailAttribute
|
config.MailAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.MailAttribute
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.DisplayNameAttribute == "" {
|
if config.DisplayNameAttribute == "" {
|
||||||
configuration.DisplayNameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.DisplayNameAttribute
|
config.DisplayNameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.DisplayNameAttribute
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ func TestShouldRaiseErrorWhenBothBackendsProvided(t *testing.T) {
|
||||||
ValidateAuthenticationBackend(&backendConfig, validator)
|
ValidateAuthenticationBackend(&backendConfig, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldRaiseErrorWhenNoBackendProvided(t *testing.T) {
|
||||||
|
@ -33,19 +33,19 @@ func TestShouldRaiseErrorWhenNoBackendProvided(t *testing.T) {
|
||||||
ValidateAuthenticationBackend(&backendConfig, validator)
|
ValidateAuthenticationBackend(&backendConfig, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 1)
|
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 {
|
type FileBasedAuthenticationBackend struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
configuration schema.AuthenticationBackendConfiguration
|
config schema.AuthenticationBackendConfiguration
|
||||||
validator *schema.StructValidator
|
validator *schema.StructValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FileBasedAuthenticationBackend) SetupTest() {
|
func (suite *FileBasedAuthenticationBackend) SetupTest() {
|
||||||
suite.validator = schema.NewStructValidator()
|
suite.validator = schema.NewStructValidator()
|
||||||
suite.configuration = schema.AuthenticationBackendConfiguration{}
|
suite.config = schema.AuthenticationBackendConfiguration{}
|
||||||
suite.configuration.File = &schema.FileAuthenticationBackendConfiguration{Path: "/a/path", Password: &schema.PasswordConfiguration{
|
suite.config.File = &schema.FileAuthenticationBackendConfiguration{Path: "/a/path", Password: &schema.PasswordConfiguration{
|
||||||
Algorithm: schema.DefaultPasswordConfiguration.Algorithm,
|
Algorithm: schema.DefaultPasswordConfiguration.Algorithm,
|
||||||
Iterations: schema.DefaultPasswordConfiguration.Iterations,
|
Iterations: schema.DefaultPasswordConfiguration.Iterations,
|
||||||
Parallelism: schema.DefaultPasswordConfiguration.Parallelism,
|
Parallelism: schema.DefaultPasswordConfiguration.Parallelism,
|
||||||
|
@ -53,150 +53,150 @@ func (suite *FileBasedAuthenticationBackend) SetupTest() {
|
||||||
KeyLength: schema.DefaultPasswordConfiguration.KeyLength,
|
KeyLength: schema.DefaultPasswordConfiguration.KeyLength,
|
||||||
SaltLength: schema.DefaultPasswordConfiguration.SaltLength,
|
SaltLength: schema.DefaultPasswordConfiguration.SaltLength,
|
||||||
}}
|
}}
|
||||||
suite.configuration.File.Password.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
|
suite.config.File.Password.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
|
||||||
}
|
}
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldValidateCompleteConfiguration() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenNoPathProvided() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenMemoryNotMoreThanEightTimesParallelism() {
|
||||||
suite.configuration.File.Password.Memory = 8
|
suite.config.File.Password.Memory = 8
|
||||||
suite.configuration.File.Password.Parallelism = 2
|
suite.config.File.Password.Parallelism = 2
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasWarnings())
|
suite.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.config.File.Password.KeyLength)
|
||||||
suite.Assert().Equal(0, suite.configuration.File.Password.Iterations)
|
suite.Assert().Equal(0, suite.config.File.Password.Iterations)
|
||||||
suite.Assert().Equal(0, suite.configuration.File.Password.SaltLength)
|
suite.Assert().Equal(0, suite.config.File.Password.SaltLength)
|
||||||
suite.Assert().Equal("", suite.configuration.File.Password.Algorithm)
|
suite.Assert().Equal("", suite.config.File.Password.Algorithm)
|
||||||
suite.Assert().Equal(0, suite.configuration.File.Password.Memory)
|
suite.Assert().Equal(0, suite.config.File.Password.Memory)
|
||||||
suite.Assert().Equal(0, suite.configuration.File.Password.Parallelism)
|
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().False(suite.validator.HasWarnings())
|
||||||
suite.Assert().Len(suite.validator.Errors(), 0)
|
suite.Assert().Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.KeyLength, suite.configuration.File.Password.KeyLength)
|
suite.Assert().Equal(schema.DefaultPasswordConfiguration.KeyLength, suite.config.File.Password.KeyLength)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.configuration.File.Password.Iterations)
|
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.config.File.Password.Iterations)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.SaltLength, suite.configuration.File.Password.SaltLength)
|
suite.Assert().Equal(schema.DefaultPasswordConfiguration.SaltLength, suite.config.File.Password.SaltLength)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.configuration.File.Password.Algorithm)
|
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.config.File.Password.Algorithm)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Memory, suite.configuration.File.Password.Memory)
|
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Memory, suite.config.File.Password.Memory)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Parallelism, suite.configuration.File.Password.Parallelism)
|
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Parallelism, suite.config.File.Password.Parallelism)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultConfigurationWhenOnlySHA512Set() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultConfigurationWhenOnlySHA512Set() {
|
||||||
suite.configuration.File.Password = &schema.PasswordConfiguration{}
|
suite.config.File.Password = &schema.PasswordConfiguration{}
|
||||||
suite.Assert().Equal("", suite.configuration.File.Password.Algorithm)
|
suite.Assert().Equal("", suite.config.File.Password.Algorithm)
|
||||||
suite.configuration.File.Password.Algorithm = "sha512"
|
suite.config.File.Password.Algorithm = "sha512"
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasWarnings())
|
suite.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Assert().Len(suite.validator.Errors(), 0)
|
suite.Assert().Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.KeyLength, suite.configuration.File.Password.KeyLength)
|
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.KeyLength, suite.config.File.Password.KeyLength)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Iterations, suite.configuration.File.Password.Iterations)
|
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Iterations, suite.config.File.Password.Iterations)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.SaltLength, suite.configuration.File.Password.SaltLength)
|
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.SaltLength, suite.config.File.Password.SaltLength)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Algorithm, suite.configuration.File.Password.Algorithm)
|
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Algorithm, suite.config.File.Password.Algorithm)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Memory, suite.configuration.File.Password.Memory)
|
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Memory, suite.config.File.Password.Memory)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Parallelism, suite.configuration.File.Password.Parallelism)
|
suite.Assert().Equal(schema.DefaultPasswordSHA512Configuration.Parallelism, suite.config.File.Password.Parallelism)
|
||||||
}
|
}
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenKeyLengthTooLow() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultValues() {
|
||||||
suite.configuration.File.Password.Algorithm = ""
|
suite.config.File.Password.Algorithm = ""
|
||||||
suite.configuration.File.Password.Iterations = 0
|
suite.config.File.Password.Iterations = 0
|
||||||
suite.configuration.File.Password.SaltLength = 0
|
suite.config.File.Password.SaltLength = 0
|
||||||
suite.configuration.File.Password.Memory = 0
|
suite.config.File.Password.Memory = 0
|
||||||
suite.configuration.File.Password.Parallelism = 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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
|
|
||||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.configuration.File.Password.Algorithm)
|
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Algorithm, suite.config.File.Password.Algorithm)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.configuration.File.Password.Iterations)
|
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Iterations, suite.config.File.Password.Iterations)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.SaltLength, suite.configuration.File.Password.SaltLength)
|
suite.Assert().Equal(schema.DefaultPasswordConfiguration.SaltLength, suite.config.File.Password.SaltLength)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Memory, suite.configuration.File.Password.Memory)
|
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Memory, suite.config.File.Password.Memory)
|
||||||
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Parallelism, suite.configuration.File.Password.Parallelism)
|
suite.Assert().Equal(schema.DefaultPasswordConfiguration.Parallelism, suite.config.File.Password.Parallelism)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileBasedAuthenticationBackend(t *testing.T) {
|
func TestFileBasedAuthenticationBackend(t *testing.T) {
|
||||||
|
@ -205,289 +205,265 @@ func TestFileBasedAuthenticationBackend(t *testing.T) {
|
||||||
|
|
||||||
type LDAPAuthenticationBackendSuite struct {
|
type LDAPAuthenticationBackendSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
configuration schema.AuthenticationBackendConfiguration
|
config schema.AuthenticationBackendConfiguration
|
||||||
validator *schema.StructValidator
|
validator *schema.StructValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) SetupTest() {
|
func (suite *LDAPAuthenticationBackendSuite) SetupTest() {
|
||||||
suite.validator = schema.NewStructValidator()
|
suite.validator = schema.NewStructValidator()
|
||||||
suite.configuration = schema.AuthenticationBackendConfiguration{}
|
suite.config = schema.AuthenticationBackendConfiguration{}
|
||||||
suite.configuration.LDAP = &schema.LDAPAuthenticationBackendConfiguration{}
|
suite.config.LDAP = &schema.LDAPAuthenticationBackendConfiguration{}
|
||||||
suite.configuration.LDAP.Implementation = schema.LDAPImplementationCustom
|
suite.config.LDAP.Implementation = schema.LDAPImplementationCustom
|
||||||
suite.configuration.LDAP.URL = testLDAPURL
|
suite.config.LDAP.URL = testLDAPURL
|
||||||
suite.configuration.LDAP.User = testLDAPUser
|
suite.config.LDAP.User = testLDAPUser
|
||||||
suite.configuration.LDAP.Password = testLDAPPassword
|
suite.config.LDAP.Password = testLDAPPassword
|
||||||
suite.configuration.LDAP.BaseDN = testLDAPBaseDN
|
suite.config.LDAP.BaseDN = testLDAPBaseDN
|
||||||
suite.configuration.LDAP.UsernameAttribute = "uid"
|
suite.config.LDAP.UsernameAttribute = "uid"
|
||||||
suite.configuration.LDAP.UsersFilter = "({username_attribute}={input})"
|
suite.config.LDAP.UsersFilter = "({username_attribute}={input})"
|
||||||
suite.configuration.LDAP.GroupsFilter = "(cn={input})"
|
suite.config.LDAP.GroupsFilter = "(cn={input})"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateCompleteConfiguration() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() {
|
||||||
suite.configuration.LDAP.Implementation = ""
|
suite.config.LDAP.Implementation = ""
|
||||||
suite.configuration.LDAP.UsernameAttribute = ""
|
suite.config.LDAP.UsernameAttribute = ""
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementationIsInvalidMSAD() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenURLNotProvided() {
|
||||||
suite.configuration.LDAP.URL = ""
|
suite.config.LDAP.URL = ""
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasWarnings())
|
suite.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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().False(suite.validator.HasWarnings())
|
||||||
suite.Assert().Len(suite.validator.Errors(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseOnBadRefreshInterval() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
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() {
|
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.config.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.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().False(suite.validator.HasWarnings())
|
||||||
suite.Assert().True(suite.validator.HasErrors())
|
suite.Assert().True(suite.validator.HasErrors())
|
||||||
|
|
||||||
suite.Require().Len(suite.validator.Errors(), 2)
|
suite.Require().Len(suite.validator.Errors(), 4)
|
||||||
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication backend ldap users filter must "+
|
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")
|
||||||
"not contain removed placeholders, {0} has been replaced with {input}")
|
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()[1], "authentication backend ldap groups filter must "+
|
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")
|
||||||
"not contain removed placeholders, "+
|
suite.Assert().EqualError(suite.validator.Errors()[3], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it is required")
|
||||||
"{0} has been replaced with {input} and {1} has been replaced with {username}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttribute() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
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() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
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() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
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() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
|
|
||||||
suite.Assert().Equal("5m", suite.configuration.RefreshInterval)
|
suite.Assert().Equal("5m", suite.config.RefreshInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesNotContainEnclosingParenthesis() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesNotContainUsernameAttribute() {
|
||||||
suite.configuration.LDAP.UsersFilter = "(&({mail_attribute}={input})(objectClass=person))"
|
suite.config.LDAP.UsersFilter = "(&({mail_attribute}={input})(objectClass=person))"
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasWarnings())
|
suite.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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")
|
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) 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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultTLSMinimumVersion() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
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() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowInvalidTLSValue() {
|
||||||
suite.configuration.LDAP.TLS = &schema.TLSConfig{
|
suite.config.LDAP.TLS = &schema.TLSConfig{
|
||||||
MinimumVersion: "SSL2.0",
|
MinimumVersion: "SSL2.0",
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasWarnings())
|
suite.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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) {
|
func TestLdapAuthenticationBackend(t *testing.T) {
|
||||||
|
@ -496,83 +472,101 @@ func TestLdapAuthenticationBackend(t *testing.T) {
|
||||||
|
|
||||||
type ActiveDirectoryAuthenticationBackendSuite struct {
|
type ActiveDirectoryAuthenticationBackendSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
configuration schema.AuthenticationBackendConfiguration
|
config schema.AuthenticationBackendConfiguration
|
||||||
validator *schema.StructValidator
|
validator *schema.StructValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() {
|
func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() {
|
||||||
suite.validator = schema.NewStructValidator()
|
suite.validator = schema.NewStructValidator()
|
||||||
suite.configuration = schema.AuthenticationBackendConfiguration{}
|
suite.config = schema.AuthenticationBackendConfiguration{}
|
||||||
suite.configuration.LDAP = &schema.LDAPAuthenticationBackendConfiguration{}
|
suite.config.LDAP = &schema.LDAPAuthenticationBackendConfiguration{}
|
||||||
suite.configuration.LDAP.Implementation = schema.LDAPImplementationActiveDirectory
|
suite.config.LDAP.Implementation = schema.LDAPImplementationActiveDirectory
|
||||||
suite.configuration.LDAP.URL = testLDAPURL
|
suite.config.LDAP.URL = testLDAPURL
|
||||||
suite.configuration.LDAP.User = testLDAPUser
|
suite.config.LDAP.User = testLDAPUser
|
||||||
suite.configuration.LDAP.Password = testLDAPPassword
|
suite.config.LDAP.Password = testLDAPPassword
|
||||||
suite.configuration.LDAP.BaseDN = testLDAPBaseDN
|
suite.config.LDAP.BaseDN = testLDAPBaseDN
|
||||||
suite.configuration.LDAP.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
|
suite.config.LDAP.TLS = schema.DefaultLDAPAuthenticationBackendConfiguration.TLS
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirectoryDefaults() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
|
|
||||||
suite.Assert().Equal(
|
suite.Assert().Equal(
|
||||||
schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout,
|
schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout,
|
||||||
suite.configuration.LDAP.Timeout)
|
suite.config.LDAP.Timeout)
|
||||||
suite.Assert().Equal(
|
suite.Assert().Equal(
|
||||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter,
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter,
|
||||||
suite.configuration.LDAP.UsersFilter)
|
suite.config.LDAP.UsersFilter)
|
||||||
suite.Assert().Equal(
|
suite.Assert().Equal(
|
||||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute,
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute,
|
||||||
suite.configuration.LDAP.UsernameAttribute)
|
suite.config.LDAP.UsernameAttribute)
|
||||||
suite.Assert().Equal(
|
suite.Assert().Equal(
|
||||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute,
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute,
|
||||||
suite.configuration.LDAP.DisplayNameAttribute)
|
suite.config.LDAP.DisplayNameAttribute)
|
||||||
suite.Assert().Equal(
|
suite.Assert().Equal(
|
||||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute,
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute,
|
||||||
suite.configuration.LDAP.MailAttribute)
|
suite.config.LDAP.MailAttribute)
|
||||||
suite.Assert().Equal(
|
suite.Assert().Equal(
|
||||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter,
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter,
|
||||||
suite.configuration.LDAP.GroupsFilter)
|
suite.config.LDAP.GroupsFilter)
|
||||||
suite.Assert().Equal(
|
suite.Assert().Equal(
|
||||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute,
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute,
|
||||||
suite.configuration.LDAP.GroupNameAttribute)
|
suite.config.LDAP.GroupNameAttribute)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
||||||
suite.configuration.LDAP.Timeout = time.Second * 2
|
suite.config.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.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
|
||||||
suite.configuration.LDAP.UsernameAttribute = "cn"
|
suite.config.LDAP.UsernameAttribute = "cn"
|
||||||
suite.configuration.LDAP.MailAttribute = "userPrincipalName"
|
suite.config.LDAP.MailAttribute = "userPrincipalName"
|
||||||
suite.configuration.LDAP.DisplayNameAttribute = "name"
|
suite.config.LDAP.DisplayNameAttribute = "name"
|
||||||
suite.configuration.LDAP.GroupsFilter = "(&(member={dn})(objectClass=group)(objectCategory=group))"
|
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=group)(objectCategory=group))"
|
||||||
suite.configuration.LDAP.GroupNameAttribute = "distinguishedName"
|
suite.config.LDAP.GroupNameAttribute = "distinguishedName"
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().NotEqual(
|
suite.Assert().NotEqual(
|
||||||
schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout,
|
schema.DefaultLDAPAuthenticationBackendConfiguration.Timeout,
|
||||||
suite.configuration.LDAP.Timeout)
|
suite.config.LDAP.Timeout)
|
||||||
suite.Assert().NotEqual(
|
suite.Assert().NotEqual(
|
||||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter,
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter,
|
||||||
suite.configuration.LDAP.UsersFilter)
|
suite.config.LDAP.UsersFilter)
|
||||||
suite.Assert().NotEqual(
|
suite.Assert().NotEqual(
|
||||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute,
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute,
|
||||||
suite.configuration.LDAP.UsernameAttribute)
|
suite.config.LDAP.UsernameAttribute)
|
||||||
suite.Assert().NotEqual(
|
suite.Assert().NotEqual(
|
||||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute,
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute,
|
||||||
suite.configuration.LDAP.DisplayNameAttribute)
|
suite.config.LDAP.DisplayNameAttribute)
|
||||||
suite.Assert().NotEqual(
|
suite.Assert().NotEqual(
|
||||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute,
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute,
|
||||||
suite.configuration.LDAP.MailAttribute)
|
suite.config.LDAP.MailAttribute)
|
||||||
suite.Assert().NotEqual(
|
suite.Assert().NotEqual(
|
||||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter,
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter,
|
||||||
suite.configuration.LDAP.GroupsFilter)
|
suite.config.LDAP.GroupsFilter)
|
||||||
suite.Assert().NotEqual(
|
suite.Assert().NotEqual(
|
||||||
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute,
|
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) {
|
func TestActiveDirectoryAuthenticationBackend(t *testing.T) {
|
||||||
|
|
|
@ -3,68 +3,59 @@ package validator
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateConfiguration and adapt the configuration read from file.
|
// ValidateConfiguration and adapt the configuration read from file.
|
||||||
func ValidateConfiguration(configuration *schema.Configuration, validator *schema.StructValidator) {
|
func ValidateConfiguration(config *schema.Configuration, validator *schema.StructValidator) {
|
||||||
if configuration.CertificatesDirectory != "" {
|
var err error
|
||||||
info, err := os.Stat(configuration.CertificatesDirectory)
|
|
||||||
if err != nil {
|
if config.CertificatesDirectory != "" {
|
||||||
validator.Push(fmt.Errorf("Error checking certificate directory: %v", err))
|
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() {
|
} 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 == "" {
|
if config.JWTSecret == "" {
|
||||||
validator.Push(fmt.Errorf("Provide a JWT secret using \"jwt_secret\" key"))
|
validator.Push(fmt.Errorf("option 'jwt_secret' is required"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.DefaultRedirectionURL != "" {
|
if config.DefaultRedirectionURL != "" {
|
||||||
err := utils.IsStringAbsURL(configuration.DefaultRedirectionURL)
|
if err = utils.IsStringAbsURL(config.DefaultRedirectionURL); err != nil {
|
||||||
if 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://'")))
|
||||||
validator.Push(fmt.Errorf("Value for \"default_redirection_url\" is invalid: %+v", err))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
ValidateRegulation(config, validator)
|
||||||
configuration.Regulation = &schema.DefaultRegulationConfiguration
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
ValidateIdentityProviders(&config.IdentityProviders, validator)
|
||||||
validator.Push(fmt.Errorf("A notifier configuration must be provided"))
|
|
||||||
} else {
|
|
||||||
ValidateNotifier(configuration.Notifier, validator)
|
|
||||||
}
|
|
||||||
|
|
||||||
ValidateIdentityProviders(&configuration.IdentityProviders, validator)
|
ValidateNTP(config, validator)
|
||||||
|
|
||||||
if configuration.NTP == nil {
|
|
||||||
configuration.NTP = &schema.DefaultNTPConfiguration
|
|
||||||
}
|
|
||||||
|
|
||||||
ValidateNTP(configuration.NTP, validator)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ func TestShouldEnsureNotifierConfigIsProvided(t *testing.T) {
|
||||||
|
|
||||||
ValidateConfiguration(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
require.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldAddDefaultAccessControl(t *testing.T) {
|
||||||
|
@ -84,8 +84,8 @@ func TestShouldRaiseErrorWithUndefinedJWTSecretKey(t *testing.T) {
|
||||||
require.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 1)
|
||||||
require.Len(t, validator.Warnings(), 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.Errors()[0], "option 'jwt_secret' is required")
|
||||||
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 TestShouldRaiseErrorWithBadDefaultRedirectionURL(t *testing.T) {
|
func TestShouldRaiseErrorWithBadDefaultRedirectionURL(t *testing.T) {
|
||||||
|
@ -97,8 +97,8 @@ func TestShouldRaiseErrorWithBadDefaultRedirectionURL(t *testing.T) {
|
||||||
require.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 1)
|
||||||
require.Len(t, validator.Warnings(), 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.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], "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 TestShouldNotOverrideCertificatesDirectoryAndShouldPassWhenBlank(t *testing.T) {
|
func TestShouldNotOverrideCertificatesDirectoryAndShouldPassWhenBlank(t *testing.T) {
|
||||||
|
@ -112,7 +112,7 @@ func TestShouldNotOverrideCertificatesDirectoryAndShouldPassWhenBlank(t *testing
|
||||||
|
|
||||||
require.Equal(t, "", config.CertificatesDirectory)
|
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) {
|
func TestShouldRaiseErrorOnInvalidCertificatesDirectory(t *testing.T) {
|
||||||
|
@ -126,12 +126,12 @@ func TestShouldRaiseErrorOnInvalidCertificatesDirectory(t *testing.T) {
|
||||||
require.Len(t, validator.Warnings(), 1)
|
require.Len(t, validator.Warnings(), 1)
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
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 {
|
} 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()
|
validator = schema.NewStructValidator()
|
||||||
config.CertificatesDirectory = "const.go"
|
config.CertificatesDirectory = "const.go"
|
||||||
|
@ -141,8 +141,8 @@ func TestShouldRaiseErrorOnInvalidCertificatesDirectory(t *testing.T) {
|
||||||
require.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 1)
|
||||||
require.Len(t, validator.Warnings(), 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.Errors()[0], "the location 'certificates_directory' refers to 'const.go' 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.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) {
|
func TestShouldNotRaiseErrorOnValidCertificatesDirectory(t *testing.T) {
|
||||||
|
@ -155,5 +155,5 @@ func TestShouldNotRaiseErrorOnValidCertificatesDirectory(t *testing.T) {
|
||||||
assert.Len(t, validator.Errors(), 0)
|
assert.Len(t, validator.Errors(), 0)
|
||||||
require.Len(t, validator.Warnings(), 1)
|
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
|
package validator
|
||||||
|
|
||||||
import "regexp"
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/oidc"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
loopback = "127.0.0.1"
|
loopback = "127.0.0.1"
|
||||||
|
@ -46,64 +50,173 @@ const (
|
||||||
|
|
||||||
// Notifier Error constants.
|
// Notifier Error constants.
|
||||||
const (
|
const (
|
||||||
errFmtNotifierMultipleConfigured = "notifier: you can't configure more than one notifier, please ensure " +
|
errFmtNotifierMultipleConfigured = "notifier: please ensure only one of the 'smtp' or 'filesystem' notifier is configured"
|
||||||
"only 'smtp' or 'filesystem' is configured"
|
errFmtNotifierNotConfigured = "notifier: you must ensure either the 'smtp' or 'filesystem' notifier " +
|
||||||
errFmtNotifierNotConfigured = "notifier: you must ensure either the 'smtp' or 'filesystem' notifier " +
|
|
||||||
"is configured"
|
"is configured"
|
||||||
errFmtNotifierFileSystemFileNameNotConfigured = "filesystem notifier: the 'filename' must be configured"
|
errFmtNotifierFileSystemFileNameNotConfigured = "notifier: filesystem: option 'filename' is required "
|
||||||
errFmtNotifierSMTPNotConfigured = "smtp notifier: the '%s' must be configured"
|
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.
|
// TOTP Error constants.
|
||||||
const (
|
const (
|
||||||
errFmtTOTPInvalidAlgorithm = "totp: algorithm '%s' is invalid: must be one of %s"
|
errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of '%s' but it is configured as '%s'"
|
||||||
errFmtTOTPInvalidPeriod = "totp: period '%d' is invalid: must be 15 or more"
|
errFmtTOTPInvalidPeriod = "totp: option 'period' option must be 15 or more but it is configured as '%d'"
|
||||||
errFmtTOTPInvalidDigits = "totp: digits '%d' is invalid: must be 6 or 8"
|
errFmtTOTPInvalidDigits = "totp: option 'digits' must be 6 or 8 but it is configured as '%d'"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Storage Error constants.
|
// Storage Error constants.
|
||||||
const (
|
const (
|
||||||
errStrStorage = "storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided"
|
errStrStorage = "storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided"
|
||||||
errStrStorageEncryptionKeyMustBeProvided = "storage: 'encryption_key' configuration option must be provided"
|
errStrStorageEncryptionKeyMustBeProvided = "storage: option 'encryption_key' must is required"
|
||||||
errStrStorageEncryptionKeyTooShort = "storage: 'encryption_key' configuration option must be 20 characters or longer"
|
errStrStorageEncryptionKeyTooShort = "storage: option 'encryption_key' must be 20 characters or longer"
|
||||||
errFmtStorageUserPassMustBeProvided = "storage: %s: 'username' and 'password' configuration options must be provided" //nolint: gosec
|
errFmtStorageUserPassMustBeProvided = "storage: %s: option 'username' and 'password' are required" //nolint: gosec
|
||||||
errFmtStorageOptionMustBeProvided = "storage: %s: '%s' configuration option must be provided"
|
errFmtStorageOptionMustBeProvided = "storage: %s: option '%s' is required"
|
||||||
errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: 'mode' configuration option '%s' is invalid: must be one of '%s'"
|
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.
|
// OpenID Error constants.
|
||||||
const (
|
const (
|
||||||
errFmtOIDCClientsDuplicateID = "openid connect provider: one or more clients have the same ID"
|
errFmtOIDCNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " +
|
||||||
errFmtOIDCClientsWithEmptyID = "openid connect provider: one or more clients have been configured with an empty ID"
|
"more clients configured"
|
||||||
errFmtOIDCNoClientsConfigured = "openid connect provider: no clients are configured"
|
errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' is required"
|
||||||
errFmtOIDCNoPrivateKey = "openid connect provider: issuer private key must be provided"
|
|
||||||
errFmtOIDCClientInvalidSecret = "openid connect provider: client with ID '%s' has an empty secret"
|
errFmtOIDCClientsDuplicateID = "identity_providers: oidc: one or more clients have the same id but all client" +
|
||||||
errFmtOIDCClientPublicInvalidSecret = "openid connect provider: client with ID '%s' is public but does not have " +
|
"id's must be unique"
|
||||||
"an empty secret"
|
errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: one or more clients have been configured with " +
|
||||||
errFmtOIDCClientRedirectURI = "openid connect provider: client with ID '%s' redirect URI %s has an " +
|
"an empty id"
|
||||||
"invalid scheme %s, should be http or https"
|
|
||||||
errFmtOIDCClientRedirectURICantBeParsed = "openid connect provider: client with ID '%s' has an invalid redirect " +
|
errFmtOIDCClientInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is required"
|
||||||
"URI '%s' could not be parsed: %v"
|
errFmtOIDCClientPublicInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is " +
|
||||||
errFmtOIDCClientRedirectURIPublic = "openid connect provider: client with ID '%s' redirect URI '%s' is " +
|
"required to be empty when option 'public' is true"
|
||||||
"only valid for the public client type, not the confidential client type"
|
errFmtOIDCClientRedirectURI = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
|
||||||
errFmtOIDCClientRedirectURIAbsolute = "openid connect provider: client with ID '%s' redirect URI '%s' is invalid " +
|
"invalid value: redirect uri '%s' must have a scheme of 'http' or 'https' but '%s' is configured"
|
||||||
"because it has no scheme when it should be http or https"
|
errFmtOIDCClientRedirectURICantBeParsed = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
|
||||||
errFmtOIDCClientInvalidPolicy = "openid connect provider: client with ID '%s' has an invalid policy " +
|
"invalid value: redirect uri '%s' could not be parsed: %v"
|
||||||
"'%s', should be either 'one_factor' or 'two_factor'"
|
errFmtOIDCClientRedirectURIPublic = "identity_providers: oidc: client '%s': option 'redirect_uris' has the" +
|
||||||
errFmtOIDCClientInvalidScope = "openid connect provider: client with ID '%s' has an invalid scope " +
|
"redirect uri '%s' when option 'public' is false but this is invalid as this uri is not valid " +
|
||||||
"'%s', must be one of: '%s'"
|
"for the openid connect confidential client type"
|
||||||
errFmtOIDCClientInvalidGrantType = "openid connect provider: client with ID '%s' has an invalid grant type " +
|
errFmtOIDCClientRedirectURIAbsolute = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
|
||||||
"'%s', must be one of: '%s'"
|
"invalid value: redirect uri '%s' must have the scheme 'http' or 'https' but it has no scheme"
|
||||||
errFmtOIDCClientInvalidResponseMode = "openid connect provider: client with ID '%s' has an invalid response mode " +
|
errFmtOIDCClientInvalidPolicy = "identity_providers: oidc: client '%s': option 'policy' must be 'one_factor' " +
|
||||||
"'%s', must be one of: '%s'"
|
"or 'two_factor' but it is configured as '%s'"
|
||||||
errFmtOIDCClientInvalidUserinfoAlgorithm = "openid connect provider: client with ID '%s' has an invalid userinfo signing " +
|
errFmtOIDCClientInvalidEntry = "identity_providers: oidc: client '%s': option '%s' must only have the values " +
|
||||||
"algorithm '%s', must be one of: '%s'"
|
"'%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 " +
|
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"
|
"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.
|
// Error constants.
|
||||||
const (
|
const (
|
||||||
/*
|
/*
|
||||||
|
@ -118,26 +231,25 @@ const (
|
||||||
|
|
||||||
errFmtReplacedConfigurationKey = "invalid configuration key '%s' was replaced by '%s'"
|
errFmtReplacedConfigurationKey = "invalid configuration key '%s' was replaced by '%s'"
|
||||||
|
|
||||||
errFmtLoggingLevelInvalid = "the log level '%s' is invalid, must be one of: %s"
|
errFmtLoggingLevelInvalid = "log: option 'level' must be one of '%s' but it is configured as '%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"
|
|
||||||
|
|
||||||
errFileHashing = "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password"
|
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"
|
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"
|
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 validStoragePostgreSQLSSLModes = []string{testModeDisabled, "require", "verify-ca", "verify-full"}
|
||||||
var validHTTPRequestMethods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
|
|
||||||
|
|
||||||
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 validOIDCGrantTypes = []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"}
|
||||||
var validOIDCResponseModes = []string{"form_post", "query", "fragment"}
|
var validOIDCResponseModes = []string{"form_post", "query", "fragment"}
|
||||||
var validOIDCUserinfoAlgorithms = []string{"none", "RS256"}
|
var validOIDCUserinfoAlgorithms = []string{"none", "RS256"}
|
||||||
|
|
|
@ -11,55 +11,55 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateIdentityProviders validates and update IdentityProviders configuration.
|
// ValidateIdentityProviders validates and update IdentityProviders configuration.
|
||||||
func ValidateIdentityProviders(configuration *schema.IdentityProvidersConfiguration, validator *schema.StructValidator) {
|
func ValidateIdentityProviders(config *schema.IdentityProvidersConfiguration, validator *schema.StructValidator) {
|
||||||
validateOIDC(configuration.OIDC, validator)
|
validateOIDC(config.OIDC, validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDC(configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
|
func validateOIDC(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration != nil {
|
if config != nil {
|
||||||
if configuration.IssuerPrivateKey == "" {
|
if config.IssuerPrivateKey == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
|
validator.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.AccessTokenLifespan == time.Duration(0) {
|
if config.AccessTokenLifespan == time.Duration(0) {
|
||||||
configuration.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
|
config.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.AuthorizeCodeLifespan == time.Duration(0) {
|
if config.AuthorizeCodeLifespan == time.Duration(0) {
|
||||||
configuration.AuthorizeCodeLifespan = schema.DefaultOpenIDConnectConfiguration.AuthorizeCodeLifespan
|
config.AuthorizeCodeLifespan = schema.DefaultOpenIDConnectConfiguration.AuthorizeCodeLifespan
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.IDTokenLifespan == time.Duration(0) {
|
if config.IDTokenLifespan == time.Duration(0) {
|
||||||
configuration.IDTokenLifespan = schema.DefaultOpenIDConnectConfiguration.IDTokenLifespan
|
config.IDTokenLifespan = schema.DefaultOpenIDConnectConfiguration.IDTokenLifespan
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.RefreshTokenLifespan == time.Duration(0) {
|
if config.RefreshTokenLifespan == time.Duration(0) {
|
||||||
configuration.RefreshTokenLifespan = schema.DefaultOpenIDConnectConfiguration.RefreshTokenLifespan
|
config.RefreshTokenLifespan = schema.DefaultOpenIDConnectConfiguration.RefreshTokenLifespan
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.MinimumParameterEntropy != 0 && configuration.MinimumParameterEntropy < 8 {
|
if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
|
||||||
validator.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, configuration.MinimumParameterEntropy))
|
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))
|
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
|
invalidID, duplicateIDs := false, false
|
||||||
|
|
||||||
var ids []string
|
var ids []string
|
||||||
|
|
||||||
for c, client := range configuration.Clients {
|
for c, client := range config.Clients {
|
||||||
if client.ID == "" {
|
if client.ID == "" {
|
||||||
invalidID = true
|
invalidID = true
|
||||||
} else {
|
} else {
|
||||||
if client.Description == "" {
|
if client.Description == "" {
|
||||||
configuration.Clients[c].Description = client.ID
|
config.Clients[c].Description = client.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.IsStringInSliceFold(client.ID, ids) {
|
if utils.IsStringInSliceFold(client.ID, ids) {
|
||||||
|
@ -79,16 +79,16 @@ func validateOIDCClients(configuration *schema.OpenIDConnectConfiguration, valid
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.Policy == "" {
|
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 {
|
} else if client.Policy != policyOneFactor && client.Policy != policyTwoFactor {
|
||||||
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy))
|
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy))
|
||||||
}
|
}
|
||||||
|
|
||||||
validateOIDCClientScopes(c, configuration, validator)
|
validateOIDCClientScopes(c, config, validator)
|
||||||
validateOIDCClientGrantTypes(c, configuration, validator)
|
validateOIDCClientGrantTypes(c, config, validator)
|
||||||
validateOIDCClientResponseTypes(c, configuration, validator)
|
validateOIDCClientResponseTypes(c, config, validator)
|
||||||
validateOIDCClientResponseModes(c, configuration, validator)
|
validateOIDCClientResponseModes(c, config, validator)
|
||||||
validateOIDDClientUserinfoAlgorithm(c, configuration, validator)
|
validateOIDDClientUserinfoAlgorithm(c, config, validator)
|
||||||
|
|
||||||
validateOIDCClientRedirectURIs(client, validator)
|
validateOIDCClientRedirectURIs(client, validator)
|
||||||
}
|
}
|
||||||
|
@ -115,8 +115,8 @@ func validateOIDCClientScopes(c int, configuration *schema.OpenIDConnectConfigur
|
||||||
for _, scope := range configuration.Clients[c].Scopes {
|
for _, scope := range configuration.Clients[c].Scopes {
|
||||||
if !utils.IsStringInSlice(scope, validOIDCScopes) {
|
if !utils.IsStringInSlice(scope, validOIDCScopes) {
|
||||||
validator.Push(fmt.Errorf(
|
validator.Push(fmt.Errorf(
|
||||||
errFmtOIDCClientInvalidScope,
|
errFmtOIDCClientInvalidEntry,
|
||||||
configuration.Clients[c].ID, scope, strings.Join(validOIDCScopes, "', '")))
|
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 {
|
for _, grantType := range configuration.Clients[c].GrantTypes {
|
||||||
if !utils.IsStringInSlice(grantType, validOIDCGrantTypes) {
|
if !utils.IsStringInSlice(grantType, validOIDCGrantTypes) {
|
||||||
validator.Push(fmt.Errorf(
|
validator.Push(fmt.Errorf(
|
||||||
errFmtOIDCClientInvalidGrantType,
|
errFmtOIDCClientInvalidEntry,
|
||||||
configuration.Clients[c].ID, grantType, strings.Join(validOIDCGrantTypes, "', '")))
|
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 {
|
for _, responseMode := range configuration.Clients[c].ResponseModes {
|
||||||
if !utils.IsStringInSlice(responseMode, validOIDCResponseModes) {
|
if !utils.IsStringInSlice(responseMode, validOIDCResponseModes) {
|
||||||
validator.Push(fmt.Errorf(
|
validator.Push(fmt.Errorf(
|
||||||
errFmtOIDCClientInvalidResponseMode,
|
errFmtOIDCClientInvalidEntry,
|
||||||
configuration.Clients[c].ID, responseMode, strings.Join(validOIDCResponseModes, "', '")))
|
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
|
configuration.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlgorithm
|
||||||
} else if !utils.IsStringInSlice(configuration.Clients[c].UserinfoSigningAlgorithm, validOIDCUserinfoAlgorithms) {
|
} else if !utils.IsStringInSlice(configuration.Clients[c].UserinfoSigningAlgorithm, validOIDCUserinfoAlgorithms) {
|
||||||
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidUserinfoAlgorithm,
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
validator.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, client.ID, redirectURI))
|
validator.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, client.ID, oauth2InstalledApp))
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,8 +173,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadScopes(t *testing.T) {
|
||||||
ValidateIdentityProviders(config, validator)
|
ValidateIdentityProviders(config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 1)
|
||||||
assert.EqualError(t, validator.Errors()[0], "openid connect provider: client with ID 'good_id' has an invalid scope "+
|
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'")
|
||||||
"'bad_scope', must be one of: 'openid', 'email', 'profile', 'groups', 'offline_access'")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T) {
|
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T) {
|
||||||
|
@ -200,9 +199,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T)
|
||||||
ValidateIdentityProviders(config, validator)
|
ValidateIdentityProviders(config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 1)
|
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 "+
|
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'")
|
||||||
"'bad_grant_type', must be one of: 'implicit', 'refresh_token', 'authorization_code', "+
|
|
||||||
"'password', 'client_credentials'")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing.T) {
|
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing.T) {
|
||||||
|
@ -228,8 +225,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing
|
||||||
ValidateIdentityProviders(config, validator)
|
ValidateIdentityProviders(config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 1)
|
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 "+
|
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'")
|
||||||
"'bad_responsemode', must be one of: 'form_post', 'query', 'fragment'")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T) {
|
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T) {
|
||||||
|
@ -255,8 +251,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T
|
||||||
ValidateIdentityProviders(config, validator)
|
ValidateIdentityProviders(config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 1)
|
||||||
assert.EqualError(t, validator.Errors()[0], "openid connect provider: client with ID 'good_id' has an invalid userinfo "+
|
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'")
|
||||||
"signing algorithm 'rs256', must be one of: 'none, RS256'")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateIdentityProvidersShouldRaiseWarningOnSecurityIssue(t *testing.T) {
|
func TestValidateIdentityProvidersShouldRaiseWarningOnSecurityIssue(t *testing.T) {
|
||||||
|
@ -502,8 +497,8 @@ func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing
|
||||||
assert.Len(t, validator.Warnings(), 0)
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
assert.Len(t, validator.Errors(), 2)
|
assert.Len(t, validator.Errors(), 2)
|
||||||
assert.ElementsMatch(t, validator.Errors(), []error{
|
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("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("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 '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()
|
validator := schema.NewStructValidator()
|
||||||
|
|
||||||
ValidateLogging(config, validator)
|
ValidateLog(config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Warnings(), 0)
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
assert.Len(t, validator.Errors(), 0)
|
assert.Len(t, validator.Errors(), 0)
|
||||||
|
@ -35,10 +35,10 @@ func TestShouldRaiseErrorOnInvalidLoggingLevel(t *testing.T) {
|
||||||
|
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
|
|
||||||
ValidateLogging(config, validator)
|
ValidateLog(config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Warnings(), 0)
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
require.Len(t, validator.Errors(), 1)
|
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.
|
// ValidateNotifier validates and update notifier configuration.
|
||||||
func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *schema.StructValidator) {
|
func ValidateNotifier(config *schema.NotifierConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.SMTP == nil && configuration.FileSystem == nil {
|
if config == nil || (config.SMTP == nil && config.FileSystem == nil) {
|
||||||
validator.Push(fmt.Errorf(errFmtNotifierNotConfigured))
|
validator.Push(fmt.Errorf(errFmtNotifierNotConfigured))
|
||||||
|
|
||||||
return
|
return
|
||||||
} else if configuration.SMTP != nil && configuration.FileSystem != nil {
|
} else if config.SMTP != nil && config.FileSystem != nil {
|
||||||
validator.Push(fmt.Errorf(errFmtNotifierMultipleConfigured))
|
validator.Push(fmt.Errorf(errFmtNotifierMultipleConfigured))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.FileSystem != nil {
|
if config.FileSystem != nil {
|
||||||
if configuration.FileSystem.Filename == "" {
|
if config.FileSystem.Filename == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtNotifierFileSystemFileNameNotConfigured))
|
validator.Push(fmt.Errorf(errFmtNotifierFileSystemFileNameNotConfigured))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
validateSMTPNotifier(configuration.SMTP, validator)
|
validateSMTPNotifier(config.SMTP, validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSMTPNotifier(configuration *schema.SMTPNotifierConfiguration, validator *schema.StructValidator) {
|
func validateSMTPNotifier(config *schema.SMTPNotifierConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.StartupCheckAddress == "" {
|
if config.StartupCheckAddress == "" {
|
||||||
configuration.StartupCheckAddress = schema.DefaultSMTPNotifierConfiguration.StartupCheckAddress
|
config.StartupCheckAddress = schema.DefaultSMTPNotifierConfiguration.StartupCheckAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Host == "" {
|
if config.Host == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtNotifierSMTPNotConfigured, "host"))
|
validator.Push(fmt.Errorf(errFmtNotifierSMTPNotConfigured, "host"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Port == 0 {
|
if config.Port == 0 {
|
||||||
validator.Push(fmt.Errorf(errFmtNotifierSMTPNotConfigured, "port"))
|
validator.Push(fmt.Errorf(errFmtNotifierSMTPNotConfigured, "port"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Timeout == 0 {
|
if config.Timeout == 0 {
|
||||||
configuration.Timeout = schema.DefaultSMTPNotifierConfiguration.Timeout
|
config.Timeout = schema.DefaultSMTPNotifierConfiguration.Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Sender.Address == "" {
|
if config.Sender.Address == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtNotifierSMTPNotConfigured, "sender"))
|
validator.Push(fmt.Errorf(errFmtNotifierSMTPNotConfigured, "sender"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Subject == "" {
|
if config.Subject == "" {
|
||||||
configuration.Subject = schema.DefaultSMTPNotifierConfiguration.Subject
|
config.Subject = schema.DefaultSMTPNotifierConfiguration.Subject
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Identifier == "" {
|
if config.Identifier == "" {
|
||||||
configuration.Identifier = schema.DefaultSMTPNotifierConfiguration.Identifier
|
config.Identifier = schema.DefaultSMTPNotifierConfiguration.Identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.TLS == nil {
|
if config.TLS == nil {
|
||||||
configuration.TLS = schema.DefaultSMTPNotifierConfiguration.TLS
|
config.TLS = schema.DefaultSMTPNotifierConfiguration.TLS
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.TLS.ServerName == "" {
|
if config.TLS.ServerName == "" {
|
||||||
configuration.TLS.ServerName = configuration.Host
|
config.TLS.ServerName = config.Host
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,34 +12,34 @@ import (
|
||||||
|
|
||||||
type NotifierSuite struct {
|
type NotifierSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
configuration schema.NotifierConfiguration
|
config schema.NotifierConfiguration
|
||||||
validator *schema.StructValidator
|
validator *schema.StructValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *NotifierSuite) SetupTest() {
|
func (suite *NotifierSuite) SetupTest() {
|
||||||
suite.validator = schema.NewStructValidator()
|
suite.validator = schema.NewStructValidator()
|
||||||
suite.configuration.SMTP = &schema.SMTPNotifierConfiguration{
|
suite.config.SMTP = &schema.SMTPNotifierConfiguration{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Password: "password",
|
Password: "password",
|
||||||
Sender: mail.Address{Name: "Authelia", Address: "authelia@example.com"},
|
Sender: mail.Address{Name: "Authelia", Address: "authelia@example.com"},
|
||||||
Host: "example.com",
|
Host: "example.com",
|
||||||
Port: 25,
|
Port: 25,
|
||||||
}
|
}
|
||||||
suite.configuration.FileSystem = nil
|
suite.config.FileSystem = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Common Tests.
|
Common Tests.
|
||||||
*/
|
*/
|
||||||
func (suite *NotifierSuite) TestShouldEnsureAtLeastSMTPOrFilesystemIsProvided() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().True(suite.validator.HasErrors())
|
suite.Require().True(suite.validator.HasErrors())
|
||||||
|
@ -50,15 +50,15 @@ func (suite *NotifierSuite) TestShouldEnsureAtLeastSMTPOrFilesystemIsProvided()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *NotifierSuite) TestShouldEnsureEitherSMTPOrFilesystemIsProvided() {
|
func (suite *NotifierSuite) TestShouldEnsureEitherSMTPOrFilesystemIsProvided() {
|
||||||
ValidateNotifier(&suite.configuration, suite.validator)
|
ValidateNotifier(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
|
|
||||||
suite.configuration.FileSystem = &schema.FileSystemNotifierConfiguration{
|
suite.config.FileSystem = &schema.FileSystemNotifierConfiguration{
|
||||||
Filename: "test",
|
Filename: "test",
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateNotifier(&suite.configuration, suite.validator)
|
ValidateNotifier(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasWarnings())
|
suite.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().True(suite.validator.HasErrors())
|
suite.Require().True(suite.validator.HasErrors())
|
||||||
|
@ -72,43 +72,43 @@ func (suite *NotifierSuite) TestShouldEnsureEitherSMTPOrFilesystemIsProvided() {
|
||||||
SMTP Tests.
|
SMTP Tests.
|
||||||
*/
|
*/
|
||||||
func (suite *NotifierSuite) TestSMTPShouldSetTLSDefaults() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
|
|
||||||
suite.Assert().Equal("example.com", suite.configuration.SMTP.TLS.ServerName)
|
suite.Assert().Equal("example.com", suite.config.SMTP.TLS.ServerName)
|
||||||
suite.Assert().Equal("TLS1.2", suite.configuration.SMTP.TLS.MinimumVersion)
|
suite.Assert().Equal("TLS1.2", suite.config.SMTP.TLS.MinimumVersion)
|
||||||
suite.Assert().False(suite.configuration.SMTP.TLS.SkipVerify)
|
suite.Assert().False(suite.config.SMTP.TLS.SkipVerify)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *NotifierSuite) TestSMTPShouldDefaultTLSServerNameToHost() {
|
func (suite *NotifierSuite) TestSMTPShouldDefaultTLSServerNameToHost() {
|
||||||
suite.configuration.SMTP.Host = "google.com"
|
suite.config.SMTP.Host = "google.com"
|
||||||
suite.configuration.SMTP.TLS = &schema.TLSConfig{
|
suite.config.SMTP.TLS = &schema.TLSConfig{
|
||||||
MinimumVersion: "TLS1.1",
|
MinimumVersion: "TLS1.1",
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateNotifier(&suite.configuration, suite.validator)
|
ValidateNotifier(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasWarnings())
|
suite.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
|
|
||||||
suite.Assert().Equal("google.com", suite.configuration.SMTP.TLS.ServerName)
|
suite.Assert().Equal("google.com", suite.config.SMTP.TLS.ServerName)
|
||||||
suite.Assert().Equal("TLS1.1", suite.configuration.SMTP.TLS.MinimumVersion)
|
suite.Assert().Equal("TLS1.1", suite.config.SMTP.TLS.MinimumVersion)
|
||||||
suite.Assert().False(suite.configuration.SMTP.TLS.SkipVerify)
|
suite.Assert().False(suite.config.SMTP.TLS.SkipVerify)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *NotifierSuite) TestSMTPShouldEnsureHostAndPortAreProvided() {
|
func (suite *NotifierSuite) TestSMTPShouldEnsureHostAndPortAreProvided() {
|
||||||
suite.configuration.FileSystem = nil
|
suite.config.FileSystem = nil
|
||||||
ValidateNotifier(&suite.configuration, suite.validator)
|
ValidateNotifier(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasWarnings())
|
suite.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
|
|
||||||
suite.configuration.SMTP.Host = ""
|
suite.config.SMTP.Host = ""
|
||||||
suite.configuration.SMTP.Port = 0
|
suite.config.SMTP.Port = 0
|
||||||
|
|
||||||
ValidateNotifier(&suite.configuration, suite.validator)
|
ValidateNotifier(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasWarnings())
|
suite.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Assert().True(suite.validator.HasErrors())
|
suite.Assert().True(suite.validator.HasErrors())
|
||||||
|
@ -122,9 +122,9 @@ func (suite *NotifierSuite) TestSMTPShouldEnsureHostAndPortAreProvided() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *NotifierSuite) TestSMTPShouldEnsureSenderIsProvided() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().True(suite.validator.HasErrors())
|
suite.Require().True(suite.validator.HasErrors())
|
||||||
|
@ -138,18 +138,18 @@ func (suite *NotifierSuite) TestSMTPShouldEnsureSenderIsProvided() {
|
||||||
File Tests.
|
File Tests.
|
||||||
*/
|
*/
|
||||||
func (suite *NotifierSuite) TestFileShouldEnsureFilenameIsProvided() {
|
func (suite *NotifierSuite) TestFileShouldEnsureFilenameIsProvided() {
|
||||||
suite.configuration.SMTP = nil
|
suite.config.SMTP = nil
|
||||||
suite.configuration.FileSystem = &schema.FileSystemNotifierConfiguration{
|
suite.config.FileSystem = &schema.FileSystemNotifierConfiguration{
|
||||||
Filename: "test",
|
Filename: "test",
|
||||||
}
|
}
|
||||||
ValidateNotifier(&suite.configuration, suite.validator)
|
ValidateNotifier(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().False(suite.validator.HasWarnings())
|
suite.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().True(suite.validator.HasErrors())
|
suite.Require().True(suite.validator.HasErrors())
|
||||||
|
|
|
@ -8,23 +8,29 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateNTP validates and update NTP configuration.
|
// ValidateNTP validates and update NTP configuration.
|
||||||
func ValidateNTP(configuration *schema.NTPConfiguration, validator *schema.StructValidator) {
|
func ValidateNTP(config *schema.Configuration, validator *schema.StructValidator) {
|
||||||
if configuration.Address == "" {
|
if config.NTP == nil {
|
||||||
configuration.Address = schema.DefaultNTPConfiguration.Address
|
config.NTP = &schema.DefaultNTPConfiguration
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Version == 0 {
|
if config.NTP.Address == "" {
|
||||||
configuration.Version = schema.DefaultNTPConfiguration.Version
|
config.NTP.Address = schema.DefaultNTPConfiguration.Address
|
||||||
} else if configuration.Version < 3 || configuration.Version > 4 {
|
|
||||||
validator.Push(fmt.Errorf("ntp: version must be either 3 or 4"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.MaximumDesync == "" {
|
if config.NTP.Version == 0 {
|
||||||
configuration.MaximumDesync = schema.DefaultNTPConfiguration.MaximumDesync
|
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 {
|
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"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDefaultNTPConfig() schema.NTPConfiguration {
|
func newDefaultNTPConfig() schema.Configuration {
|
||||||
config := schema.NTPConfiguration{}
|
return schema.Configuration{
|
||||||
return config
|
NTP: &schema.NTPConfiguration{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldSetDefaultNtpAddress(t *testing.T) {
|
func TestShouldSetDefaultNtpAddress(t *testing.T) {
|
||||||
|
@ -20,7 +22,7 @@ func TestShouldSetDefaultNtpAddress(t *testing.T) {
|
||||||
ValidateNTP(&config, validator)
|
ValidateNTP(&config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 0)
|
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) {
|
func TestShouldSetDefaultNtpVersion(t *testing.T) {
|
||||||
|
@ -30,7 +32,7 @@ func TestShouldSetDefaultNtpVersion(t *testing.T) {
|
||||||
ValidateNTP(&config, validator)
|
ValidateNTP(&config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 0)
|
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) {
|
func TestShouldSetDefaultNtpMaximumDesync(t *testing.T) {
|
||||||
|
@ -40,7 +42,7 @@ func TestShouldSetDefaultNtpMaximumDesync(t *testing.T) {
|
||||||
ValidateNTP(&config, validator)
|
ValidateNTP(&config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 0)
|
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) {
|
func TestShouldSetDefaultNtpDisableStartupCheck(t *testing.T) {
|
||||||
|
@ -50,16 +52,29 @@ func TestShouldSetDefaultNtpDisableStartupCheck(t *testing.T) {
|
||||||
ValidateNTP(&config, validator)
|
ValidateNTP(&config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 0)
|
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) {
|
func TestShouldRaiseErrorOnMaximumDesyncString(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := newDefaultNTPConfig()
|
config := newDefaultNTPConfig()
|
||||||
config.MaximumDesync = "a second"
|
config.NTP.MaximumDesync = "a second"
|
||||||
|
|
||||||
ValidateNTP(&config, validator)
|
ValidateNTP(&config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 1)
|
require.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")
|
|
||||||
|
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.
|
// ValidateRegulation validates and update regulator configuration.
|
||||||
func ValidateRegulation(configuration *schema.RegulationConfiguration, validator *schema.StructValidator) {
|
func ValidateRegulation(config *schema.Configuration, validator *schema.StructValidator) {
|
||||||
if configuration.FindTime == "" {
|
if config.Regulation == nil {
|
||||||
configuration.FindTime = schema.DefaultRegulationConfiguration.FindTime // 2 min.
|
config.Regulation = &schema.DefaultRegulationConfiguration
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.BanTime == "" {
|
if config.Regulation.FindTime == "" {
|
||||||
configuration.BanTime = schema.DefaultRegulationConfiguration.BanTime // 5 min.
|
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 {
|
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 {
|
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 {
|
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"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDefaultRegulationConfig() schema.RegulationConfiguration {
|
func newDefaultRegulationConfig() schema.Configuration {
|
||||||
config := schema.RegulationConfiguration{}
|
config := schema.Configuration{
|
||||||
|
Regulation: &schema.RegulationConfiguration{},
|
||||||
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +23,7 @@ func TestShouldSetDefaultRegulationBanTime(t *testing.T) {
|
||||||
ValidateRegulation(&config, validator)
|
ValidateRegulation(&config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 0)
|
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) {
|
func TestShouldSetDefaultRegulationFindTime(t *testing.T) {
|
||||||
|
@ -30,30 +33,30 @@ func TestShouldSetDefaultRegulationFindTime(t *testing.T) {
|
||||||
ValidateRegulation(&config, validator)
|
ValidateRegulation(&config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 0)
|
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) {
|
func TestShouldRaiseErrorWhenFindTimeLessThanBanTime(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := newDefaultRegulationConfig()
|
config := newDefaultRegulationConfig()
|
||||||
config.FindTime = "1m"
|
config.Regulation.FindTime = "1m"
|
||||||
config.BanTime = "10s"
|
config.Regulation.BanTime = "10s"
|
||||||
|
|
||||||
ValidateRegulation(&config, validator)
|
ValidateRegulation(&config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldRaiseErrorOnBadDurationStrings(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := newDefaultRegulationConfig()
|
config := newDefaultRegulationConfig()
|
||||||
config.FindTime = "a year"
|
config.Regulation.FindTime = "a year"
|
||||||
config.BanTime = "forever"
|
config.Regulation.BanTime = "forever"
|
||||||
|
|
||||||
ValidateRegulation(&config, validator)
|
ValidateRegulation(&config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 2)
|
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()[0], "regulation: option 'find_time' could not be parsed: could not parse 'a year' as 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()[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.
|
// ValidateServer checks a server configuration is correct.
|
||||||
func ValidateServer(configuration *schema.Configuration, validator *schema.StructValidator) {
|
func ValidateServer(config *schema.Configuration, validator *schema.StructValidator) {
|
||||||
if configuration.Server.Host == "" {
|
if config.Server.Host == "" {
|
||||||
configuration.Server.Host = schema.DefaultServerConfiguration.Host
|
config.Server.Host = schema.DefaultServerConfiguration.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Server.Port == 0 {
|
if config.Server.Port == 0 {
|
||||||
configuration.Server.Port = schema.DefaultServerConfiguration.Port
|
config.Server.Port = schema.DefaultServerConfiguration.Port
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Server.TLS.Key != "" && configuration.Server.TLS.Certificate == "" {
|
if config.Server.TLS.Key != "" && config.Server.TLS.Certificate == "" {
|
||||||
validator.Push(fmt.Errorf("server: no TLS certificate provided to accompany the TLS key, please configure the 'server.tls.certificate' option"))
|
validator.Push(fmt.Errorf(errFmtServerTLSCert))
|
||||||
} else if configuration.Server.TLS.Key == "" && configuration.Server.TLS.Certificate != "" {
|
} else if config.Server.TLS.Key == "" && config.Server.TLS.Certificate != "" {
|
||||||
validator.Push(fmt.Errorf("server: no TLS key provided to accompany the TLS certificate, please configure the 'server.tls.key' option"))
|
validator.Push(fmt.Errorf(errFmtServerTLSKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(configuration.Server.Path, "/"):
|
case strings.Contains(config.Server.Path, "/"):
|
||||||
validator.Push(fmt.Errorf("server path must not contain any forward slashes"))
|
validator.Push(fmt.Errorf(errFmtServerPathNoForwardSlashes))
|
||||||
case !utils.IsStringAlphaNumeric(configuration.Server.Path):
|
case !utils.IsStringAlphaNumeric(config.Server.Path):
|
||||||
validator.Push(fmt.Errorf("server path must only be alpha numeric characters"))
|
validator.Push(fmt.Errorf(errFmtServerPathAlphaNum))
|
||||||
case configuration.Server.Path == "": // Don't do anything if it's blank.
|
case config.Server.Path == "": // Don't do anything if it's blank.
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
configuration.Server.Path = path.Clean("/" + configuration.Server.Path)
|
config.Server.Path = path.Clean("/" + config.Server.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Server.ReadBufferSize == 0 {
|
if config.Server.ReadBufferSize == 0 {
|
||||||
configuration.Server.ReadBufferSize = schema.DefaultServerConfiguration.ReadBufferSize
|
config.Server.ReadBufferSize = schema.DefaultServerConfiguration.ReadBufferSize
|
||||||
} else if configuration.Server.ReadBufferSize < 0 {
|
} else if config.Server.ReadBufferSize < 0 {
|
||||||
validator.Push(fmt.Errorf("server read buffer size must be above 0"))
|
validator.Push(fmt.Errorf(errFmtServerBufferSize, "read", config.Server.ReadBufferSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Server.WriteBufferSize == 0 {
|
if config.Server.WriteBufferSize == 0 {
|
||||||
configuration.Server.WriteBufferSize = schema.DefaultServerConfiguration.WriteBufferSize
|
config.Server.WriteBufferSize = schema.DefaultServerConfiguration.WriteBufferSize
|
||||||
} else if configuration.Server.WriteBufferSize < 0 {
|
} else if config.Server.WriteBufferSize < 0 {
|
||||||
validator.Push(fmt.Errorf("server write buffer size must be above 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)
|
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()[0], "server: option 'read_buffer_size' must be above 0 but it is configured as '-1'")
|
||||||
assert.EqualError(t, validator.Errors()[1], "server write buffer size must be above 0")
|
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) {
|
func TestShouldRaiseOnNonAlphanumericCharsInPath(t *testing.T) {
|
||||||
|
@ -123,7 +123,7 @@ func TestShouldRaiseErrorWhenTLSCertWithoutKeyIsProvided(t *testing.T) {
|
||||||
|
|
||||||
ValidateServer(&config, validator)
|
ValidateServer(&config, validator)
|
||||||
require.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldRaiseErrorWhenTLSKeyWithoutCertIsProvided(t *testing.T) {
|
||||||
|
@ -133,7 +133,7 @@ func TestShouldRaiseErrorWhenTLSKeyWithoutCertIsProvided(t *testing.T) {
|
||||||
|
|
||||||
ValidateServer(&config, validator)
|
ValidateServer(&config, validator)
|
||||||
require.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldNotRaiseErrorWhenBothTLSCertificateAndKeyAreProvided(t *testing.T) {
|
||||||
|
|
|
@ -10,109 +10,110 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateSession validates and update session configuration.
|
// ValidateSession validates and update session configuration.
|
||||||
func ValidateSession(configuration *schema.SessionConfiguration, validator *schema.StructValidator) {
|
func ValidateSession(config *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.Name == "" {
|
if config.Name == "" {
|
||||||
configuration.Name = schema.DefaultSessionConfiguration.Name
|
config.Name = schema.DefaultSessionConfiguration.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Redis != nil {
|
if config.Redis != nil {
|
||||||
if configuration.Redis.HighAvailability != nil {
|
if config.Redis.HighAvailability != nil {
|
||||||
if configuration.Redis.HighAvailability.SentinelName != "" {
|
validateRedisSentinel(config, validator)
|
||||||
validateRedisSentinel(configuration, validator)
|
|
||||||
} else {
|
|
||||||
validator.Push(fmt.Errorf("Session provider redis is configured for high availability but doesn't have a sentinel_name which is required"))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
validateRedis(configuration, validator)
|
validateRedis(config, validator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateSession(configuration, validator)
|
validateSession(config, validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSession(configuration *schema.SessionConfiguration, validator *schema.StructValidator) {
|
func validateSession(config *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.Expiration == "" {
|
if config.Expiration == "" {
|
||||||
configuration.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour.
|
config.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour.
|
||||||
} else if _, err := utils.ParseDurationString(configuration.Expiration); err != nil {
|
} else if _, err := utils.ParseDurationString(config.Expiration); err != nil {
|
||||||
validator.Push(fmt.Errorf("Error occurred parsing session expiration string: %s", err))
|
validator.Push(fmt.Errorf(errFmtSessionCouldNotParseDuration, "expiriation", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Inactivity == "" {
|
if config.Inactivity == "" {
|
||||||
configuration.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min.
|
config.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min.
|
||||||
} else if _, err := utils.ParseDurationString(configuration.Inactivity); err != nil {
|
} else if _, err := utils.ParseDurationString(config.Inactivity); err != nil {
|
||||||
validator.Push(fmt.Errorf("Error occurred parsing session inactivity string: %s", err))
|
validator.Push(fmt.Errorf(errFmtSessionCouldNotParseDuration, "inactivity", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.RememberMeDuration == "" {
|
if config.RememberMeDuration == "" {
|
||||||
configuration.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration // 1 month.
|
config.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration // 1 month.
|
||||||
} else if _, err := utils.ParseDurationString(configuration.RememberMeDuration); err != nil {
|
} else if _, err := utils.ParseDurationString(config.RememberMeDuration); err != nil {
|
||||||
validator.Push(fmt.Errorf("Error occurred parsing session remember_me_duration string: %s", err))
|
validator.Push(fmt.Errorf(errFmtSessionCouldNotParseDuration, "remember_me_duration", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Domain == "" {
|
if config.Domain == "" {
|
||||||
validator.Push(errors.New("Set domain of the session object"))
|
validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "domain"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(configuration.Domain, "*") {
|
if strings.HasPrefix(config.Domain, "*.") {
|
||||||
validator.Push(errors.New("The domain of the session must be the root domain you're protecting instead of a wildcard domain"))
|
validator.Push(fmt.Errorf(errFmtSessionDomainMustBeRoot, config.Domain))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.SameSite == "" {
|
if config.SameSite == "" {
|
||||||
configuration.SameSite = schema.DefaultSessionConfiguration.SameSite
|
config.SameSite = schema.DefaultSessionConfiguration.SameSite
|
||||||
} else if configuration.SameSite != "none" && configuration.SameSite != "lax" && configuration.SameSite != "strict" {
|
} else if !utils.IsStringInSlice(config.SameSite, validSessionSameSiteValues) {
|
||||||
validator.Push(errors.New("session same_site is configured incorrectly, must be one of 'none', 'lax', or 'strict'"))
|
validator.Push(fmt.Errorf(errFmtSessionSameSite, strings.Join(validSessionSameSiteValues, "', '"), config.SameSite))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRedis(configuration *schema.SessionConfiguration, validator *schema.StructValidator) {
|
func validateRedisCommon(config *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.Redis.Host == "" {
|
if config.Secret == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtSessionRedisHostRequired, "redis"))
|
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 == "" {
|
validateRedisCommon(config, validator)
|
||||||
validator.Push(fmt.Errorf(errFmtSessionSecretRedisProvider, "redis"))
|
|
||||||
}
|
|
||||||
|
|
||||||
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"))
|
validator.Push(errors.New("A redis port different than 0 must be provided"))
|
||||||
} else if configuration.Redis.Port < 0 || configuration.Redis.Port > 65535 {
|
} else if config.Redis.Port < 0 || config.Redis.Port > 65535 {
|
||||||
validator.Push(fmt.Errorf(errFmtSessionRedisPortRange, "redis"))
|
validator.Push(fmt.Errorf(errFmtSessionRedisPortRange, config.Redis.Port))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Redis.MaximumActiveConnections <= 0 {
|
if config.Redis.MaximumActiveConnections <= 0 {
|
||||||
configuration.Redis.MaximumActiveConnections = 8
|
config.Redis.MaximumActiveConnections = 8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRedisSentinel(configuration *schema.SessionConfiguration, validator *schema.StructValidator) {
|
func validateRedisSentinel(config *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.Redis.Port == 0 {
|
if config.Redis.HighAvailability.SentinelName == "" {
|
||||||
configuration.Redis.Port = 26379
|
validator.Push(fmt.Errorf(errFmtSessionRedisSentinelMissingName))
|
||||||
} else if configuration.Redis.Port < 0 || configuration.Redis.Port > 65535 {
|
|
||||||
validator.Push(fmt.Errorf(errFmtSessionRedisPortRange, "redis sentinel"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validateHighAvailability(configuration, validator, "redis sentinel")
|
if config.Redis.Port == 0 {
|
||||||
}
|
config.Redis.Port = 26379
|
||||||
|
} else if config.Redis.Port < 0 || config.Redis.Port > 65535 {
|
||||||
func validateHighAvailability(configuration *schema.SessionConfiguration, validator *schema.StructValidator, provider string) {
|
validator.Push(fmt.Errorf(errFmtSessionRedisPortRange, config.Redis.Port))
|
||||||
if configuration.Redis.Host == "" && len(configuration.Redis.HighAvailability.Nodes) == 0 {
|
|
||||||
validator.Push(fmt.Errorf(errFmtSessionRedisHostOrNodesRequired, provider))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Secret == "" {
|
if config.Redis.Host == "" && len(config.Redis.HighAvailability.Nodes) == 0 {
|
||||||
validator.Push(fmt.Errorf(errFmtSessionSecretRedisProvider, provider))
|
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 == "" {
|
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))
|
hostMissing = true
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.Port == 0 {
|
if node.Port == 0 {
|
||||||
if provider == "redis sentinel" {
|
config.Redis.HighAvailability.Nodes[i].Port = 26379
|
||||||
configuration.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())
|
assert.False(t, validator.HasWarnings())
|
||||||
require.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldRaiseErrorWithInvalidRedisPortHigh(t *testing.T) {
|
||||||
|
@ -117,7 +117,7 @@ func TestShouldRaiseErrorWithInvalidRedisPortHigh(t *testing.T) {
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
require.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldRaiseErrorWhenRedisIsUsedAndSecretNotSet(t *testing.T) {
|
||||||
|
@ -140,7 +140,7 @@ func TestShouldRaiseErrorWhenRedisIsUsedAndSecretNotSet(t *testing.T) {
|
||||||
|
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) {
|
||||||
|
@ -193,7 +193,7 @@ func TestShouldRaiseOneErrorWhenRedisHighAvailabilityHasNodesWithNoHost(t *testi
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
require.Len(t, errors, 1)
|
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) {
|
func TestShouldRaiseOneErrorWhenRedisHighAvailabilityDoesNotHaveSentinelName(t *testing.T) {
|
||||||
|
@ -215,7 +215,7 @@ func TestShouldRaiseOneErrorWhenRedisHighAvailabilityDoesNotHaveSentinelName(t *
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
require.Len(t, errors, 1)
|
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) {
|
func TestShouldUpdateDefaultPortWhenRedisSentinelHasNodes(t *testing.T) {
|
||||||
|
@ -281,8 +281,8 @@ func TestShouldRaiseErrorsWhenRedisSentinelOptionsIncorrectlyConfigured(t *testi
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
require.Len(t, errors, 2)
|
require.Len(t, errors, 2)
|
||||||
|
|
||||||
assert.EqualError(t, errors[0], fmt.Sprintf(errFmtSessionRedisPortRange, "redis sentinel"))
|
assert.EqualError(t, errors[0], fmt.Sprintf(errFmtSessionRedisPortRange, 65536))
|
||||||
assert.EqualError(t, errors[1], fmt.Sprintf(errFmtSessionSecretRedisProvider, "redis sentinel"))
|
assert.EqualError(t, errors[1], fmt.Sprintf(errFmtSessionSecretRequired, "redis"))
|
||||||
|
|
||||||
validator.Clear()
|
validator.Clear()
|
||||||
|
|
||||||
|
@ -295,8 +295,8 @@ func TestShouldRaiseErrorsWhenRedisSentinelOptionsIncorrectlyConfigured(t *testi
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
require.Len(t, errors, 2)
|
require.Len(t, errors, 2)
|
||||||
|
|
||||||
assert.EqualError(t, errors[0], fmt.Sprintf(errFmtSessionRedisPortRange, "redis sentinel"))
|
assert.EqualError(t, errors[0], fmt.Sprintf(errFmtSessionRedisPortRange, -1))
|
||||||
assert.EqualError(t, errors[1], fmt.Sprintf(errFmtSessionSecretRedisProvider, "redis sentinel"))
|
assert.EqualError(t, errors[1], fmt.Sprintf(errFmtSessionSecretRequired, "redis"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotRaiseErrorsAndSetDefaultPortWhenRedisSentinelPortBlank(t *testing.T) {
|
func TestShouldNotRaiseErrorsAndSetDefaultPortWhenRedisSentinelPortBlank(t *testing.T) {
|
||||||
|
@ -347,7 +347,7 @@ func TestShouldRaiseErrorWhenRedisHostAndHighAvailabilityNodesEmpty(t *testing.T
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
require.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldRaiseErrorsWhenRedisHostNotSet(t *testing.T) {
|
||||||
|
@ -365,7 +365,7 @@ func TestShouldRaiseErrorsWhenRedisHostNotSet(t *testing.T) {
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
require.Len(t, errors, 1)
|
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) {
|
func TestShouldRaiseErrorWhenDomainNotSet(t *testing.T) {
|
||||||
|
@ -377,7 +377,7 @@ func TestShouldRaiseErrorWhenDomainNotSet(t *testing.T) {
|
||||||
|
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldRaiseErrorWhenDomainIsWildcard(t *testing.T) {
|
||||||
|
@ -389,7 +389,7 @@ func TestShouldRaiseErrorWhenDomainIsWildcard(t *testing.T) {
|
||||||
|
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) {
|
||||||
|
@ -401,7 +401,7 @@ func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) {
|
||||||
|
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldNotRaiseErrorWhenSameSiteSetCorrectly(t *testing.T) {
|
||||||
|
@ -430,8 +430,8 @@ func TestShouldRaiseErrorWhenBadInactivityAndExpirationSet(t *testing.T) {
|
||||||
|
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 2)
|
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()[0], "session: option 'expiriation' could not be parsed: could not parse '-1' as 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()[1], "session: option 'inactivity' could not be parsed: could not parse '-1' as a duration")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseErrorWhenBadRememberMeDurationSet(t *testing.T) {
|
func TestShouldRaiseErrorWhenBadRememberMeDurationSet(t *testing.T) {
|
||||||
|
@ -443,7 +443,7 @@ func TestShouldRaiseErrorWhenBadRememberMeDurationSet(t *testing.T) {
|
||||||
|
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldSetDefaultRememberMeDuration(t *testing.T) {
|
||||||
|
|
|
@ -10,66 +10,66 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateStorage validates storage configuration.
|
// ValidateStorage validates storage configuration.
|
||||||
func ValidateStorage(configuration schema.StorageConfiguration, validator *schema.StructValidator) {
|
func ValidateStorage(config schema.StorageConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.Local == nil && configuration.MySQL == nil && configuration.PostgreSQL == nil {
|
if config.Local == nil && config.MySQL == nil && config.PostgreSQL == nil {
|
||||||
validator.Push(errors.New(errStrStorage))
|
validator.Push(errors.New(errStrStorage))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case configuration.MySQL != nil:
|
case config.MySQL != nil:
|
||||||
validateSQLConfiguration(&configuration.MySQL.SQLStorageConfiguration, validator, "mysql")
|
validateSQLConfiguration(&config.MySQL.SQLStorageConfiguration, validator, "mysql")
|
||||||
case configuration.PostgreSQL != nil:
|
case config.PostgreSQL != nil:
|
||||||
validatePostgreSQLConfiguration(configuration.PostgreSQL, validator)
|
validatePostgreSQLConfiguration(config.PostgreSQL, validator)
|
||||||
case configuration.Local != nil:
|
case config.Local != nil:
|
||||||
validateLocalStorageConfiguration(configuration.Local, validator)
|
validateLocalStorageConfiguration(config.Local, validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.EncryptionKey == "" {
|
if config.EncryptionKey == "" {
|
||||||
validator.Push(errors.New(errStrStorageEncryptionKeyMustBeProvided))
|
validator.Push(errors.New(errStrStorageEncryptionKeyMustBeProvided))
|
||||||
} else if len(configuration.EncryptionKey) < 20 {
|
} else if len(config.EncryptionKey) < 20 {
|
||||||
validator.Push(errors.New(errStrStorageEncryptionKeyTooShort))
|
validator.Push(errors.New(errStrStorageEncryptionKeyTooShort))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSQLConfiguration(configuration *schema.SQLStorageConfiguration, validator *schema.StructValidator, provider string) {
|
func validateSQLConfiguration(config *schema.SQLStorageConfiguration, validator *schema.StructValidator, provider string) {
|
||||||
if configuration.Timeout == 0 {
|
if config.Timeout == 0 {
|
||||||
configuration.Timeout = schema.DefaultSQLStorageConfiguration.Timeout
|
config.Timeout = schema.DefaultSQLStorageConfiguration.Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Host == "" {
|
if config.Host == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, provider, "host"))
|
validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, provider, "host"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Username == "" || configuration.Password == "" {
|
if config.Username == "" || config.Password == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtStorageUserPassMustBeProvided, provider))
|
validator.Push(fmt.Errorf(errFmtStorageUserPassMustBeProvided, provider))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Database == "" {
|
if config.Database == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, provider, "database"))
|
validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, provider, "database"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatePostgreSQLConfiguration(configuration *schema.PostgreSQLStorageConfiguration, validator *schema.StructValidator) {
|
func validatePostgreSQLConfiguration(config *schema.PostgreSQLStorageConfiguration, validator *schema.StructValidator) {
|
||||||
validateSQLConfiguration(&configuration.SQLStorageConfiguration, validator, "postgres")
|
validateSQLConfiguration(&config.SQLStorageConfiguration, validator, "postgres")
|
||||||
|
|
||||||
if configuration.Schema == "" {
|
if config.Schema == "" {
|
||||||
configuration.Schema = schema.DefaultPostgreSQLStorageConfiguration.Schema
|
config.Schema = schema.DefaultPostgreSQLStorageConfiguration.Schema
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated. TODO: Remove in v4.36.0.
|
// Deprecated. TODO: Remove in v4.36.0.
|
||||||
if configuration.SSLMode != "" && configuration.SSL.Mode == "" {
|
if config.SSLMode != "" && config.SSL.Mode == "" {
|
||||||
configuration.SSL.Mode = configuration.SSLMode
|
config.SSL.Mode = config.SSLMode
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.SSL.Mode == "" {
|
if config.SSL.Mode == "" {
|
||||||
configuration.SSL.Mode = schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode
|
config.SSL.Mode = schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode
|
||||||
} else if !utils.IsStringInSlice(configuration.SSL.Mode, storagePostgreSQLValidSSLModes) {
|
} else if !utils.IsStringInSlice(config.SSL.Mode, validStoragePostgreSQLSSLModes) {
|
||||||
validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, configuration.SSL.Mode, strings.Join(storagePostgreSQLValidSSLModes, "', '")))
|
validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, strings.Join(validStoragePostgreSQLSSLModes, "', '"), config.SSL.Mode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateLocalStorageConfiguration(configuration *schema.LocalStorageConfiguration, validator *schema.StructValidator) {
|
func validateLocalStorageConfiguration(config *schema.LocalStorageConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.Path == "" {
|
if config.Path == "" {
|
||||||
validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, "local", "path"))
|
validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, "local", "path"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,24 +10,24 @@ import (
|
||||||
|
|
||||||
type StorageSuite struct {
|
type StorageSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
configuration schema.StorageConfiguration
|
config schema.StorageConfiguration
|
||||||
validator *schema.StructValidator
|
validator *schema.StructValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *StorageSuite) SetupTest() {
|
func (suite *StorageSuite) SetupTest() {
|
||||||
suite.validator = schema.NewStructValidator()
|
suite.validator = schema.NewStructValidator()
|
||||||
suite.configuration.EncryptionKey = testEncryptionKey
|
suite.config.EncryptionKey = testEncryptionKey
|
||||||
suite.configuration.Local = nil
|
suite.config.Local = nil
|
||||||
suite.configuration.PostgreSQL = nil
|
suite.config.PostgreSQL = nil
|
||||||
suite.configuration.MySQL = nil
|
suite.config.MySQL = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *StorageSuite) TestShouldValidateOneStorageIsConfigured() {
|
func (suite *StorageSuite) TestShouldValidateOneStorageIsConfigured() {
|
||||||
suite.configuration.Local = nil
|
suite.config.Local = nil
|
||||||
suite.configuration.PostgreSQL = nil
|
suite.config.PostgreSQL = nil
|
||||||
suite.configuration.MySQL = 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.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
suite.Require().Len(suite.validator.Errors(), 1)
|
||||||
|
@ -35,37 +35,37 @@ func (suite *StorageSuite) TestShouldValidateOneStorageIsConfigured() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *StorageSuite) TestShouldValidateLocalPathIsProvided() {
|
func (suite *StorageSuite) TestShouldValidateLocalPathIsProvided() {
|
||||||
suite.configuration.Local = &schema.LocalStorageConfiguration{
|
suite.config.Local = &schema.LocalStorageConfiguration{
|
||||||
Path: "",
|
Path: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateStorage(suite.configuration, suite.validator)
|
ValidateStorage(suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Require().Len(suite.validator.Warnings(), 0)
|
suite.Require().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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.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.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 0)
|
suite.Require().Len(suite.validator.Errors(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *StorageSuite) TestShouldValidateMySQLHostUsernamePasswordAndDatabaseAreProvided() {
|
func (suite *StorageSuite) TestShouldValidateMySQLHostUsernamePasswordAndDatabaseAreProvided() {
|
||||||
suite.configuration.MySQL = &schema.MySQLStorageConfiguration{}
|
suite.config.MySQL = &schema.MySQLStorageConfiguration{}
|
||||||
ValidateStorage(suite.configuration, suite.validator)
|
ValidateStorage(suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Require().Len(suite.validator.Errors(), 3)
|
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()[0], "storage: mysql: option 'host' is required")
|
||||||
suite.Assert().EqualError(suite.validator.Errors()[1], "storage: mysql: 'username' and 'password' configuration options must be provided")
|
suite.Assert().EqualError(suite.validator.Errors()[1], "storage: mysql: option 'username' and 'password' are required")
|
||||||
suite.Assert().EqualError(suite.validator.Errors()[2], "storage: mysql: 'database' configuration option must be provided")
|
suite.Assert().EqualError(suite.validator.Errors()[2], "storage: mysql: option 'database' is required")
|
||||||
|
|
||||||
suite.validator.Clear()
|
suite.validator.Clear()
|
||||||
suite.configuration.MySQL = &schema.MySQLStorageConfiguration{
|
suite.config.MySQL = &schema.MySQLStorageConfiguration{
|
||||||
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
||||||
Host: "localhost",
|
Host: "localhost",
|
||||||
Username: "myuser",
|
Username: "myuser",
|
||||||
|
@ -73,24 +73,24 @@ func (suite *StorageSuite) TestShouldValidateMySQLHostUsernamePasswordAndDatabas
|
||||||
Database: "database",
|
Database: "database",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
ValidateStorage(suite.configuration, suite.validator)
|
ValidateStorage(suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Require().Len(suite.validator.Warnings(), 0)
|
suite.Require().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 0)
|
suite.Require().Len(suite.validator.Errors(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *StorageSuite) TestShouldValidatePostgreSQLHostUsernamePasswordAndDatabaseAreProvided() {
|
func (suite *StorageSuite) TestShouldValidatePostgreSQLHostUsernamePasswordAndDatabaseAreProvided() {
|
||||||
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{}
|
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{}
|
||||||
suite.configuration.MySQL = nil
|
suite.config.MySQL = nil
|
||||||
ValidateStorage(suite.configuration, suite.validator)
|
ValidateStorage(suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Require().Len(suite.validator.Errors(), 3)
|
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()[0], "storage: postgres: option 'host' is required")
|
||||||
suite.Assert().EqualError(suite.validator.Errors()[1], "storage: postgres: 'username' and 'password' configuration options must be provided")
|
suite.Assert().EqualError(suite.validator.Errors()[1], "storage: postgres: option 'username' and 'password' are required")
|
||||||
suite.Assert().EqualError(suite.validator.Errors()[2], "storage: postgres: 'database' configuration option must be provided")
|
suite.Assert().EqualError(suite.validator.Errors()[2], "storage: postgres: option 'database' is required")
|
||||||
|
|
||||||
suite.validator.Clear()
|
suite.validator.Clear()
|
||||||
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
||||||
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
||||||
Host: "postgre",
|
Host: "postgre",
|
||||||
Username: "myuser",
|
Username: "myuser",
|
||||||
|
@ -98,14 +98,14 @@ func (suite *StorageSuite) TestShouldValidatePostgreSQLHostUsernamePasswordAndDa
|
||||||
Database: "database",
|
Database: "database",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
ValidateStorage(suite.configuration, suite.validator)
|
ValidateStorage(suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Assert().Len(suite.validator.Errors(), 0)
|
suite.Assert().Len(suite.validator.Errors(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *StorageSuite) TestShouldValidatePostgresSSLModeAndSchemaDefaults() {
|
func (suite *StorageSuite) TestShouldValidatePostgresSSLModeAndSchemaDefaults() {
|
||||||
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
||||||
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
||||||
Host: "db1",
|
Host: "db1",
|
||||||
Username: "myuser",
|
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.Warnings(), 0)
|
||||||
suite.Assert().Len(suite.validator.Errors(), 0)
|
suite.Assert().Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Assert().Equal("disable", suite.configuration.PostgreSQL.SSL.Mode)
|
suite.Assert().Equal("disable", suite.config.PostgreSQL.SSL.Mode)
|
||||||
suite.Assert().Equal("public", suite.configuration.PostgreSQL.Schema)
|
suite.Assert().Equal("public", suite.config.PostgreSQL.Schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *StorageSuite) TestShouldValidatePostgresDefaultsDontOverrideConfiguration() {
|
func (suite *StorageSuite) TestShouldValidatePostgresDefaultsDontOverrideConfiguration() {
|
||||||
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
||||||
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
||||||
Host: "db1",
|
Host: "db1",
|
||||||
Username: "myuser",
|
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.Warnings(), 0)
|
||||||
suite.Assert().Len(suite.validator.Errors(), 0)
|
suite.Assert().Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Assert().Equal("require", suite.configuration.PostgreSQL.SSL.Mode)
|
suite.Assert().Equal("require", suite.config.PostgreSQL.SSL.Mode)
|
||||||
suite.Assert().Equal("authelia", suite.configuration.PostgreSQL.Schema)
|
suite.Assert().Equal("authelia", suite.config.PostgreSQL.Schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
|
func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
|
||||||
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
||||||
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
||||||
Host: "db2",
|
Host: "db2",
|
||||||
Username: "myuser",
|
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.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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.
|
// Deprecated. TODO: Remove in v4.36.0.
|
||||||
func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeMappedForDeprecations() {
|
func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeMappedForDeprecations() {
|
||||||
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
suite.config.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
|
||||||
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
SQLStorageConfiguration: schema.SQLStorageConfiguration{
|
||||||
Host: "pg",
|
Host: "pg",
|
||||||
Username: "myuser",
|
Username: "myuser",
|
||||||
|
@ -178,38 +178,38 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeMappedForDepre
|
||||||
SSLMode: "require",
|
SSLMode: "require",
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateStorage(suite.configuration, suite.validator)
|
ValidateStorage(suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Assert().Len(suite.validator.Errors(), 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() {
|
func (suite *StorageSuite) TestShouldRaiseErrorOnNoEncryptionKey() {
|
||||||
suite.configuration.EncryptionKey = ""
|
suite.config.EncryptionKey = ""
|
||||||
suite.configuration.Local = &schema.LocalStorageConfiguration{
|
suite.config.Local = &schema.LocalStorageConfiguration{
|
||||||
Path: "/this/is/a/path",
|
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.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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() {
|
func (suite *StorageSuite) TestShouldRaiseErrorOnShortEncryptionKey() {
|
||||||
suite.configuration.EncryptionKey = "abc"
|
suite.config.EncryptionKey = "abc"
|
||||||
suite.configuration.Local = &schema.LocalStorageConfiguration{
|
suite.config.Local = &schema.LocalStorageConfiguration{
|
||||||
Path: "/this/is/a/path",
|
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.Warnings(), 0)
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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) {
|
func TestShouldRunStorageSuite(t *testing.T) {
|
||||||
|
|
|
@ -2,20 +2,19 @@ package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"strings"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateTheme validates and update Theme configuration.
|
// ValidateTheme validates and update Theme configuration.
|
||||||
func ValidateTheme(configuration *schema.Configuration, validator *schema.StructValidator) {
|
func ValidateTheme(config *schema.Configuration, validator *schema.StructValidator) {
|
||||||
if configuration.Theme == "" {
|
if config.Theme == "" {
|
||||||
configuration.Theme = "light"
|
config.Theme = "light"
|
||||||
}
|
}
|
||||||
|
|
||||||
validThemes := regexp.MustCompile("light|dark|grey|auto")
|
if !utils.IsStringInSlice(config.Theme, validThemeNames) {
|
||||||
|
validator.Push(fmt.Errorf(errFmtThemeName, strings.Join(validThemeNames, "', '"), config.Theme))
|
||||||
if !validThemes.MatchString(configuration.Theme) {
|
|
||||||
validator.Push(fmt.Errorf("Theme: %s is not valid, valid themes are: \"light\", \"dark\", \"grey\" or \"auto\"", configuration.Theme))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,33 +10,33 @@ import (
|
||||||
|
|
||||||
type Theme struct {
|
type Theme struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
configuration *schema.Configuration
|
config *schema.Configuration
|
||||||
validator *schema.StructValidator
|
validator *schema.StructValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *Theme) SetupTest() {
|
func (suite *Theme) SetupTest() {
|
||||||
suite.validator = schema.NewStructValidator()
|
suite.validator = schema.NewStructValidator()
|
||||||
suite.configuration = &schema.Configuration{
|
suite.config = &schema.Configuration{
|
||||||
Theme: "light",
|
Theme: "light",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *Theme) TestShouldValidateCompleteConfiguration() {
|
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.HasWarnings())
|
||||||
suite.Assert().False(suite.validator.HasErrors())
|
suite.Assert().False(suite.validator.HasErrors())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *Theme) TestShouldRaiseErrorWhenInvalidThemeProvided() {
|
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.Assert().False(suite.validator.HasWarnings())
|
||||||
suite.Require().Len(suite.validator.Errors(), 1)
|
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) {
|
func TestThemes(t *testing.T) {
|
||||||
|
|
|
@ -9,40 +9,40 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateTOTP validates and update TOTP configuration.
|
// ValidateTOTP validates and update TOTP configuration.
|
||||||
func ValidateTOTP(configuration *schema.Configuration, validator *schema.StructValidator) {
|
func ValidateTOTP(config *schema.Configuration, validator *schema.StructValidator) {
|
||||||
if configuration.TOTP == nil {
|
if config.TOTP == nil {
|
||||||
configuration.TOTP = &schema.DefaultTOTPConfiguration
|
config.TOTP = &schema.DefaultTOTPConfiguration
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.TOTP.Issuer == "" {
|
if config.TOTP.Issuer == "" {
|
||||||
configuration.TOTP.Issuer = schema.DefaultTOTPConfiguration.Issuer
|
config.TOTP.Issuer = schema.DefaultTOTPConfiguration.Issuer
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.TOTP.Algorithm == "" {
|
if config.TOTP.Algorithm == "" {
|
||||||
configuration.TOTP.Algorithm = schema.DefaultTOTPConfiguration.Algorithm
|
config.TOTP.Algorithm = schema.DefaultTOTPConfiguration.Algorithm
|
||||||
} else {
|
} 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) {
|
if !utils.IsStringInSlice(config.TOTP.Algorithm, schema.TOTPPossibleAlgorithms) {
|
||||||
validator.Push(fmt.Errorf(errFmtTOTPInvalidAlgorithm, configuration.TOTP.Algorithm, strings.Join(schema.TOTPPossibleAlgorithms, ", ")))
|
validator.Push(fmt.Errorf(errFmtTOTPInvalidAlgorithm, strings.Join(schema.TOTPPossibleAlgorithms, "', '"), config.TOTP.Algorithm))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.TOTP.Period == 0 {
|
if config.TOTP.Period == 0 {
|
||||||
configuration.TOTP.Period = schema.DefaultTOTPConfiguration.Period
|
config.TOTP.Period = schema.DefaultTOTPConfiguration.Period
|
||||||
} else if configuration.TOTP.Period < 15 {
|
} else if config.TOTP.Period < 15 {
|
||||||
validator.Push(fmt.Errorf(errFmtTOTPInvalidPeriod, configuration.TOTP.Period))
|
validator.Push(fmt.Errorf(errFmtTOTPInvalidPeriod, config.TOTP.Period))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.TOTP.Digits == 0 {
|
if config.TOTP.Digits == 0 {
|
||||||
configuration.TOTP.Digits = schema.DefaultTOTPConfiguration.Digits
|
config.TOTP.Digits = schema.DefaultTOTPConfiguration.Digits
|
||||||
} else if configuration.TOTP.Digits != 6 && configuration.TOTP.Digits != 8 {
|
} else if config.TOTP.Digits != 6 && config.TOTP.Digits != 8 {
|
||||||
validator.Push(fmt.Errorf(errFmtTOTPInvalidDigits, configuration.TOTP.Digits))
|
validator.Push(fmt.Errorf(errFmtTOTPInvalidDigits, config.TOTP.Digits))
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.TOTP.Skew == nil {
|
if config.TOTP.Skew == nil {
|
||||||
configuration.TOTP.Skew = schema.DefaultTOTPConfiguration.Skew
|
config.TOTP.Skew = schema.DefaultTOTPConfiguration.Skew
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ func TestShouldRaiseErrorWhenInvalidTOTPAlgorithm(t *testing.T) {
|
||||||
ValidateTOTP(config, validator)
|
ValidateTOTP(config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldRaiseErrorWhenInvalidTOTPValues(t *testing.T) {
|
||||||
|
|
|
@ -10,16 +10,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestShouldCheckNTP(t *testing.T) {
|
func TestShouldCheckNTP(t *testing.T) {
|
||||||
config := schema.NTPConfiguration{
|
config := &schema.Configuration{
|
||||||
Address: "time.cloudflare.com:123",
|
NTP: &schema.NTPConfiguration{
|
||||||
Version: 4,
|
Address: "time.cloudflare.com:123",
|
||||||
MaximumDesync: "3s",
|
Version: 4,
|
||||||
DisableStartupCheck: false,
|
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())
|
assert.NoError(t, ntp.StartupCheck())
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,21 @@ access_control:
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
- domain: "singlefactor.example.com"
|
- domain: "singlefactor.example.com"
|
||||||
policy: one_factor
|
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:
|
notifier:
|
||||||
filesystem:
|
filesystem:
|
||||||
|
|
|
@ -66,15 +66,15 @@ func (s *CLISuite) TestShouldPrintVersion() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CLISuite) TestShouldValidateConfig() {
|
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().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() {
|
func (s *CLISuite) TestShouldFailValidateConfig() {
|
||||||
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "validate-config", "/config/invalid.yml"})
|
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "validate-config", "--config", "/config/invalid.yml"})
|
||||||
s.Assert().NotNil(err)
|
s.Assert().NoError(err)
|
||||||
s.Assert().Contains(output, "Error Loading Configuration: stat /config/invalid.yml: no such file or directory")
|
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() {
|
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"})
|
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info"})
|
||||||
s.Assert().EqualError(err, "exit status 1")
|
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"})
|
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "history"})
|
||||||
s.Assert().EqualError(err, "exit status 1")
|
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() {
|
func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() {
|
||||||
|
@ -382,6 +382,94 @@ func (s *CLISuite) TestStorage05ShouldMigrateDown() {
|
||||||
s.Regexp(pattern1, output)
|
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) {
|
func TestCLISuite(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("skipping suite test in short mode")
|
t.Skip("skipping suite test in short mode")
|
||||||
|
|
|
@ -68,12 +68,12 @@ func TestShouldReturnZeroAndErrorOnInvalidTLSVersions(t *testing.T) {
|
||||||
version, err := TLSStringToTLSConfigVersion("TLS1.4")
|
version, err := TLSStringToTLSConfigVersion("TLS1.4")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, uint16(0), version)
|
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")
|
version, err = TLSStringToTLSConfigVersion("SSL3.0")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, uint16(0), version)
|
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) {
|
func TestShouldReturnErrWhenX509DirectoryNotExist(t *testing.T) {
|
||||||
|
|
|
@ -73,4 +73,4 @@ var htmlEscaper = strings.NewReplacer(
|
||||||
var ErrTimeoutReached = errors.New("timeout reached")
|
var ErrTimeoutReached = errors.New("timeout reached")
|
||||||
|
|
||||||
// ErrTLSVersionNotSupported returned when an unknown TLS version supplied.
|
// 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:
|
case input == "0" || len(matches) == 3:
|
||||||
seconds, err := strconv.Atoi(input)
|
seconds, err := strconv.Atoi(input)
|
||||||
if err != nil {
|
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
|
duration = time.Duration(seconds) * time.Second
|
||||||
case input != "":
|
case input != "":
|
||||||
// Throw this error if input is anything other than a blank string, blank string will default to a duration of nothing.
|
// 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
|
return duration, nil
|
||||||
|
|
|
@ -47,25 +47,25 @@ func TestShouldParseSecondsString(t *testing.T) {
|
||||||
|
|
||||||
func TestShouldNotParseDurationStringWithOutOfOrderQuantitiesAndUnits(t *testing.T) {
|
func TestShouldNotParseDurationStringWithOutOfOrderQuantitiesAndUnits(t *testing.T) {
|
||||||
duration, err := ParseDurationString("h1")
|
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)
|
assert.Equal(t, time.Duration(0), duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotParseBadDurationString(t *testing.T) {
|
func TestShouldNotParseBadDurationString(t *testing.T) {
|
||||||
duration, err := ParseDurationString("10x")
|
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)
|
assert.Equal(t, time.Duration(0), duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotParseDurationStringWithMultiValueUnits(t *testing.T) {
|
func TestShouldNotParseDurationStringWithMultiValueUnits(t *testing.T) {
|
||||||
duration, err := ParseDurationString("10ms")
|
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)
|
assert.Equal(t, time.Duration(0), duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotParseDurationStringWithLeadingZero(t *testing.T) {
|
func TestShouldNotParseDurationStringWithLeadingZero(t *testing.T) {
|
||||||
duration, err := ParseDurationString("005h")
|
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)
|
assert.Equal(t, time.Duration(0), duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue