[FEATURE] Config Validation (#901)
* [FEATURE] Config Validation * check configuration for invalid keys on startup * allow users to manually trigger all configuration validation on a file using a cmd * setup all defaults in config template and run tests against it to prevent accidents * use tests to check bad configuration values are caught * use tests to check old configuration values are caught * add tests for specific key errors * resolve merge conflicts * nolint prealloc for testpull/902/head^2
parent
b9fb33d806
commit
c1ac25a15b
|
@ -130,8 +130,9 @@ func main() {
|
|||
},
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(versionCmd, commands.HashPasswordCmd)
|
||||
rootCmd.AddCommand(commands.CertificatesCmd)
|
||||
rootCmd.AddCommand(versionCmd, commands.HashPasswordCmd,
|
||||
commands.ValidateConfigCmd, commands.CertificatesCmd)
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -382,10 +382,10 @@ notifier:
|
|||
# {title} is replaced by the text from the notifier
|
||||
subject: "[Authelia] {title}"
|
||||
# This address is used during the startup check to verify the email configuration is correct. It's not important what it is except if your email server only allows local delivery.
|
||||
## startup_check_address: test@authelia.com
|
||||
## trusted_cert: ""
|
||||
## disable_require_tls: false
|
||||
## disable_verify_cert: false
|
||||
startup_check_address: test@authelia.com
|
||||
trusted_cert: ""
|
||||
disable_require_tls: false
|
||||
disable_verify_cert: false
|
||||
|
||||
# Sending an email using a Gmail account is as simple as the next section.
|
||||
# You need to create an app password by following: https://support.google.com/accounts/answer/185833?hl=en
|
||||
|
|
|
@ -14,7 +14,23 @@ When running **Authelia**, you can specify your configuration by passing
|
|||
the file path as shown below.
|
||||
|
||||
$ authelia --config config.custom.yml
|
||||
|
||||
|
||||
## Validation
|
||||
|
||||
Authelia validates the configuration when it starts. This process checks multiple factors including configuration keys
|
||||
that don't exist, configuration keys that have changed, the values of the keys are valid, and that a configuration
|
||||
key isn't supplied at the same time as a secret for the same configuration option.
|
||||
|
||||
You may also optionally validate your configuration against this validation process manually by using the validate-config
|
||||
option with the Authelia binary as shown below. Keep in mind if you're using [secrets](./secrets.md) you will have to
|
||||
manually provide these if you don't want to get certain validation errors (specifically requesting you provide one of
|
||||
the secret values). You can choose to ignore them if you know what you're doing. This command is useful prior to
|
||||
upgrading to prevent configuration changes from impacting downtime in an upgrade.
|
||||
|
||||
$ authelia validate-config configuration.yml
|
||||
|
||||
|
||||
## Duration Notation Format
|
||||
|
||||
We have implemented a string based notation for configuration options that take a duration. This section describes its
|
||||
|
|
1
go.sum
1
go.sum
|
@ -458,6 +458,7 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/
|
|||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/authelia/authelia/internal/configuration"
|
||||
)
|
||||
|
||||
// ValidateConfigCmd uses the internal configuration reader to validate the configuration.
|
||||
var ValidateConfigCmd = &cobra.Command{
|
||||
Use: "validate-config [yaml]",
|
||||
Short: "Check a configuration against the internal configuration validation mechanisms.",
|
||||
Run: func(cobraCmd *cobra.Command, args []string) {
|
||||
configPath := args[0]
|
||||
if _, err := os.Stat(configPath); err != nil {
|
||||
log.Fatalf("Error Loading Configuration: %s\n", err)
|
||||
}
|
||||
|
||||
// TODO: Actually use the configuration to validate some providers like Notifier
|
||||
_, errs := configuration.Read(configPath)
|
||||
if len(errs) != 0 {
|
||||
str := "Errors"
|
||||
if len(errs) == 1 {
|
||||
str = "Error"
|
||||
}
|
||||
errors := ""
|
||||
for _, err := range errs {
|
||||
errors += fmt.Sprintf("\t%s\n", err.Error())
|
||||
}
|
||||
log.Fatalf("%s occurred parsing configuration:\n%s", str, errors)
|
||||
} else {
|
||||
log.Println("Configuration parsed successfully without errors.")
|
||||
}
|
||||
},
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
}
|
|
@ -48,6 +48,7 @@ func Read(configPath string) (*schema.Configuration, []error) {
|
|||
val := schema.NewStructValidator()
|
||||
validator.ValidateSecrets(&configuration, val, viper.GetViper())
|
||||
validator.ValidateConfiguration(&configuration, val)
|
||||
validator.ValidateKeys(val, viper.AllKeys())
|
||||
|
||||
if val.HasErrors() {
|
||||
return nil, val.Errors()
|
||||
|
|
|
@ -2,6 +2,7 @@ package configuration
|
|||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -29,6 +30,7 @@ func TestShouldParseConfigFile(t *testing.T) {
|
|||
require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_MYSQL_PASSWORD", "mysql_secret_from_env"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD", "postgres_secret_from_env"))
|
||||
|
||||
config, errors := Read("./test_resources/config.yml")
|
||||
|
||||
|
@ -73,6 +75,33 @@ func TestShouldParseAltConfigFile(t *testing.T) {
|
|||
assert.Len(t, config.AccessControl.Rules, 12)
|
||||
}
|
||||
|
||||
func TestShouldNotParseConfigFileWithOldOrUnexpectedKeys(t *testing.T) {
|
||||
require.NoError(t, os.Setenv("AUTHELIA_JWT_SECRET", "secret_from_env"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_DUO_API_SECRET_KEY", "duo_secret_from_env"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_SESSION_SECRET", "session_secret_from_env"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD", "ldap_secret_from_env"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_MYSQL_PASSWORD", "mysql_secret_from_env"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD", "postgres_secret_from_env"))
|
||||
|
||||
_, errors := Read("./test_resources/config_bad_keys.yml")
|
||||
require.Len(t, errors, 2)
|
||||
|
||||
// Sort error slice to prevent shenanigans that somehow occur
|
||||
sort.Slice(errors, func(i, j int) bool {
|
||||
return errors[i].Error() < errors[j].Error()
|
||||
})
|
||||
assert.EqualError(t, errors[0], "config key not expected: loggy_file")
|
||||
assert.EqualError(t, errors[1], "config key replaced: logs_level is now log_level")
|
||||
}
|
||||
|
||||
func TestShouldValidateConfigurationTemplate(t *testing.T) {
|
||||
resetEnv()
|
||||
_, errors := Read("../../config.template.yml")
|
||||
assert.Len(t, errors, 0)
|
||||
}
|
||||
|
||||
func TestShouldOnlyAllowOneEnvType(t *testing.T) {
|
||||
resetEnv()
|
||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD", "postgres_secret_from_env"))
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
###############################################################
|
||||
# Authelia configuration #
|
||||
###############################################################
|
||||
|
||||
host: 127.0.0.1
|
||||
port: 9091
|
||||
loggy_file: /etc/authelia/svc.log
|
||||
|
||||
logs_level: debug
|
||||
default_redirection_url: https://home.example.com:8080/
|
||||
|
||||
totp:
|
||||
issuer: authelia.com
|
||||
|
||||
duo_api:
|
||||
hostname: api-123456789.example.com
|
||||
integration_key: ABCDEF
|
||||
|
||||
authentication_backend:
|
||||
ldap:
|
||||
url: ldap://127.0.0.1
|
||||
base_dn: dc=example,dc=com
|
||||
username_attribute: uid
|
||||
additional_users_dn: ou=users
|
||||
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
|
||||
additional_groups_dn: ou=groups
|
||||
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
||||
group_name_attribute: cn
|
||||
mail_attribute: mail
|
||||
user: cn=admin,dc=example,dc=com
|
||||
|
||||
access_control:
|
||||
default_policy: deny
|
||||
|
||||
rules:
|
||||
# Rules applied to everyone
|
||||
- domain: public.example.com
|
||||
policy: bypass
|
||||
|
||||
- domain: secure.example.com
|
||||
policy: one_factor
|
||||
# Network based rule, if not provided any network matches.
|
||||
networks:
|
||||
- 192.168.1.0/24
|
||||
- domain: secure.example.com
|
||||
policy: two_factor
|
||||
|
||||
- domain: [singlefactor.example.com, onefactor.example.com]
|
||||
policy: one_factor
|
||||
|
||||
# Rules applied to 'admins' group
|
||||
- domain: "mx2.mail.example.com"
|
||||
subject: "group:admins"
|
||||
policy: deny
|
||||
- domain: "*.example.com"
|
||||
subject: "group:admins"
|
||||
policy: two_factor
|
||||
|
||||
# Rules applied to 'dev' group
|
||||
- domain: dev.example.com
|
||||
resources:
|
||||
- "^/groups/dev/.*$"
|
||||
subject: "group:dev"
|
||||
policy: two_factor
|
||||
|
||||
# Rules applied to user 'john'
|
||||
- domain: dev.example.com
|
||||
resources:
|
||||
- "^/users/john/.*$"
|
||||
subject: "user:john"
|
||||
policy: two_factor
|
||||
|
||||
# Rules applied to 'dev' group and user 'john'
|
||||
- domain: dev.example.com
|
||||
resources:
|
||||
- "^/deny-all.*$"
|
||||
subject: ["group:dev", "user:john"]
|
||||
policy: denied
|
||||
|
||||
# Rules applied to user 'harry'
|
||||
- domain: dev.example.com
|
||||
resources:
|
||||
- "^/users/harry/.*$"
|
||||
subject: "user:harry"
|
||||
policy: two_factor
|
||||
|
||||
# Rules applied to user 'bob'
|
||||
- domain: "*.mail.example.com"
|
||||
subject: "user:bob"
|
||||
policy: two_factor
|
||||
- domain: "dev.example.com"
|
||||
resources:
|
||||
- "^/users/bob/.*$"
|
||||
subject: "user:bob"
|
||||
policy: two_factor
|
||||
|
||||
session:
|
||||
name: authelia_session
|
||||
expiration: 3600000 # 1 hour
|
||||
inactivity: 300000 # 5 minutes
|
||||
domain: example.com
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
|
||||
regulation:
|
||||
max_retries: 3
|
||||
find_time: 120
|
||||
ban_time: 300
|
||||
|
||||
storage:
|
||||
mysql:
|
||||
host: 127.0.0.1
|
||||
port: 3306
|
||||
database: authelia
|
||||
username: authelia
|
||||
|
||||
notifier:
|
||||
smtp:
|
||||
username: test
|
||||
host: 127.0.0.1
|
||||
port: 1025
|
||||
sender: admin@example.com
|
||||
disable_require_tls: true
|
|
@ -0,0 +1,148 @@
|
|||
package validator
|
||||
|
||||
var validKeys = []string{
|
||||
// Root Keys.
|
||||
"host",
|
||||
"port",
|
||||
"log_level",
|
||||
"log_file_path",
|
||||
"default_redirection_url",
|
||||
"jwt_secret",
|
||||
"tls_key",
|
||||
"tls_cert",
|
||||
"google_analytics",
|
||||
|
||||
// TOTP Keys
|
||||
"totp.issuer",
|
||||
"totp.period",
|
||||
"totp.skew",
|
||||
|
||||
// Access Control Keys
|
||||
"access_control.rules",
|
||||
"access_control.default_policy",
|
||||
|
||||
// Session Keys.
|
||||
"session.name",
|
||||
"session.secret",
|
||||
"session.expiration",
|
||||
"session.inactivity",
|
||||
"session.remember_me_duration",
|
||||
"session.domain",
|
||||
|
||||
// Redis Session Keys.
|
||||
"session.redis.host",
|
||||
"session.redis.port",
|
||||
"session.redis.password",
|
||||
"session.redis.database_index",
|
||||
|
||||
// Local Storage Keys.
|
||||
"storage.local.path",
|
||||
|
||||
// MySQL Storage Keys.
|
||||
"storage.mysql.host",
|
||||
"storage.mysql.port",
|
||||
"storage.mysql.database",
|
||||
"storage.mysql.username",
|
||||
"storage.mysql.password",
|
||||
|
||||
// PostgreSQL Storage Keys.
|
||||
"storage.postgres.host",
|
||||
"storage.postgres.port",
|
||||
"storage.postgres.database",
|
||||
"storage.postgres.username",
|
||||
"storage.postgres.password",
|
||||
"storage.postgres.sslmode",
|
||||
|
||||
// FileSystem Notifier Keys.
|
||||
"notifier.filesystem.filename",
|
||||
"notifier.disable_startup_check",
|
||||
|
||||
// SMTP Notifier Keys.
|
||||
"notifier.smtp.username",
|
||||
"notifier.smtp.password",
|
||||
"notifier.smtp.host",
|
||||
"notifier.smtp.port",
|
||||
"notifier.smtp.sender",
|
||||
"notifier.smtp.subject",
|
||||
"notifier.smtp.startup_check_address",
|
||||
"notifier.smtp.disable_require_tls",
|
||||
"notifier.smtp.disable_verify_cert",
|
||||
"notifier.smtp.trusted_cert",
|
||||
|
||||
// Regulation Keys.
|
||||
"regulation.max_retries",
|
||||
"regulation.find_time",
|
||||
"regulation.ban_time",
|
||||
|
||||
// DUO API Keys.
|
||||
"duo_api.hostname",
|
||||
"duo_api.integration_key",
|
||||
"duo_api.secret_key",
|
||||
|
||||
// Authentication Backend Keys.
|
||||
"authentication_backend.disable_reset_password",
|
||||
|
||||
// LDAP Authentication Backend Keys.
|
||||
"authentication_backend.ldap.url",
|
||||
"authentication_backend.ldap.skip_verify",
|
||||
"authentication_backend.ldap.base_dn",
|
||||
"authentication_backend.ldap.username_attribute",
|
||||
"authentication_backend.ldap.additional_users_dn",
|
||||
"authentication_backend.ldap.users_filter",
|
||||
"authentication_backend.ldap.additional_groups_dn",
|
||||
"authentication_backend.ldap.groups_filter",
|
||||
"authentication_backend.ldap.group_name_attribute",
|
||||
"authentication_backend.ldap.mail_attribute",
|
||||
"authentication_backend.ldap.user",
|
||||
"authentication_backend.ldap.password",
|
||||
|
||||
// File Authentication Backend Keys.
|
||||
"authentication_backend.file.path",
|
||||
"authentication_backend.file.password.algorithm",
|
||||
"authentication_backend.file.password.iterations",
|
||||
"authentication_backend.file.password.key_length",
|
||||
"authentication_backend.file.password.salt_length",
|
||||
"authentication_backend.file.password.memory",
|
||||
"authentication_backend.file.password.parallelism",
|
||||
|
||||
// Secret Keys.
|
||||
"authelia.jwt_secret",
|
||||
"authelia.duo_api.secret_key",
|
||||
"authelia.session.secret",
|
||||
"authelia.authentication_backend.ldap.password",
|
||||
"authelia.notifier.smtp.password",
|
||||
"authelia.session.redis.password",
|
||||
"authelia.storage.mysql.password",
|
||||
"authelia.storage.postgres.password",
|
||||
"authelia.jwt_secret.file",
|
||||
"authelia.duo_api.secret_key.file",
|
||||
"authelia.session.secret.file",
|
||||
"authelia.authentication_backend.ldap.password.file",
|
||||
"authelia.notifier.smtp.password.file",
|
||||
"authelia.session.redis.password.file",
|
||||
"authelia.storage.mysql.password.file",
|
||||
"authelia.storage.postgres.password.file",
|
||||
}
|
||||
|
||||
var specificErrorKeys = map[string]string{
|
||||
"logs_file_path": "config key replaced: logs_file is now log_file",
|
||||
"logs_level": "config key replaced: logs_level is now log_level",
|
||||
"authentication_backend.file.password_options.algorithm": "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password",
|
||||
"authentication_backend.file.password_options.iterations": "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password",
|
||||
"authentication_backend.file.password_options.key_length": "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password",
|
||||
"authentication_backend.file.password_options.salt_length": "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password",
|
||||
"authentication_backend.file.password_options.memory": "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password",
|
||||
"authentication_backend.file.password_options.parallelism": "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password",
|
||||
"authentication_backend.file.password_hashing.algorithm": "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password",
|
||||
"authentication_backend.file.password_hashing.iterations": "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password",
|
||||
"authentication_backend.file.password_hashing.key_length": "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password",
|
||||
"authentication_backend.file.password_hashing.salt_length": "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password",
|
||||
"authentication_backend.file.password_hashing.memory": "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password",
|
||||
"authentication_backend.file.password_hashing.parallelism": "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password",
|
||||
"authentication_backend.file.hashing.algorithm": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||
"authentication_backend.file.hashing.iterations": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||
"authentication_backend.file.hashing.key_length": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||
"authentication_backend.file.hashing.salt_length": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||
"authentication_backend.file.hashing.memory": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||
"authentication_backend.file.hashing.parallelism": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/internal/utils"
|
||||
)
|
||||
|
||||
func ValidateKeys(validator *schema.StructValidator, keys []string) {
|
||||
var errStrings []string
|
||||
for _, key := range keys {
|
||||
if utils.IsStringInSlice(key, validKeys) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err, ok := specificErrorKeys[key]; ok {
|
||||
if !utils.IsStringInSlice(err, errStrings) {
|
||||
errStrings = append(errStrings, err)
|
||||
}
|
||||
} else {
|
||||
validator.Push(fmt.Errorf("config key not expected: %s", key))
|
||||
}
|
||||
}
|
||||
for _, err := range errStrings {
|
||||
validator.Push(errors.New(err))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/internal/utils"
|
||||
)
|
||||
|
||||
func TestShouldValidateGoodKeys(t *testing.T) {
|
||||
configKeys := validKeys
|
||||
val := schema.NewStructValidator()
|
||||
ValidateKeys(val, configKeys)
|
||||
|
||||
require.Len(t, val.Errors(), 0)
|
||||
}
|
||||
|
||||
func TestShouldNotValidateBadKeys(t *testing.T) {
|
||||
configKeys := validKeys
|
||||
configKeys = append(configKeys, "bad_key")
|
||||
configKeys = append(configKeys, "totp.skewy")
|
||||
val := schema.NewStructValidator()
|
||||
ValidateKeys(val, configKeys)
|
||||
|
||||
errs := val.Errors()
|
||||
require.Len(t, errs, 2)
|
||||
|
||||
assert.EqualError(t, errs[0], "config key not expected: bad_key")
|
||||
assert.EqualError(t, errs[1], "config key not expected: totp.skewy")
|
||||
}
|
||||
|
||||
func TestAllSpecificErrorKeys(t *testing.T) {
|
||||
var configKeys []string //nolint:prealloc // This is because the test is dynamic based on the keys that exist in the map
|
||||
var uniqueValues []string
|
||||
|
||||
// Setup configKeys and uniqueValues expected.
|
||||
for key, value := range specificErrorKeys {
|
||||
configKeys = append(configKeys, key)
|
||||
if !utils.IsStringInSlice(value, uniqueValues) {
|
||||
uniqueValues = append(uniqueValues, value)
|
||||
}
|
||||
}
|
||||
|
||||
val := schema.NewStructValidator()
|
||||
ValidateKeys(val, configKeys)
|
||||
|
||||
errs := val.Errors()
|
||||
|
||||
// Check only unique errors are shown. Require because if we don't the next test panics.
|
||||
require.Len(t, errs, len(uniqueValues))
|
||||
|
||||
// Dynamically check all specific errors.
|
||||
for i, value := range uniqueValues {
|
||||
assert.EqualError(t, errs[i], value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecificErrorKeys(t *testing.T) {
|
||||
configKeys := []string{
|
||||
"logs_level",
|
||||
"logs_file_path",
|
||||
"authentication_backend.file.password_options.algorithm",
|
||||
"authentication_backend.file.password_options.iterations", // This should not show another error since our target for the specific error is password_options.
|
||||
"authentication_backend.file.password_hashing.algorithm",
|
||||
"authentication_backend.file.hashing.algorithm",
|
||||
}
|
||||
|
||||
val := schema.NewStructValidator()
|
||||
ValidateKeys(val, configKeys)
|
||||
|
||||
errs := val.Errors()
|
||||
|
||||
require.Len(t, errs, 5)
|
||||
|
||||
assert.EqualError(t, errs[0], specificErrorKeys["logs_level"])
|
||||
assert.EqualError(t, errs[1], specificErrorKeys["logs_file_path"])
|
||||
assert.EqualError(t, errs[2], specificErrorKeys["authentication_backend.file.password_options.iterations"])
|
||||
assert.EqualError(t, errs[3], specificErrorKeys["authentication_backend.file.password_hashing.algorithm"])
|
||||
assert.EqualError(t, errs[4], specificErrorKeys["authentication_backend.file.hashing.algorithm"])
|
||||
}
|
Loading…
Reference in New Issue