feat(configuration): automatically map old keys (#3199)
This performs automatic remapping of deprecated configuration keys in most situations.pull/3614/head
parent
ab1d0c51d3
commit
d2f1e5d36d
|
@ -216,11 +216,10 @@ ntp:
|
||||||
##
|
##
|
||||||
## The available providers are: `file`, `ldap`. You must use only one of these providers.
|
## The available providers are: `file`, `ldap`. You must use only one of these providers.
|
||||||
authentication_backend:
|
authentication_backend:
|
||||||
## Disable both the HTML element and the API for reset password functionality.
|
|
||||||
disable_reset_password: false
|
|
||||||
|
|
||||||
## Password Reset Options.
|
## Password Reset Options.
|
||||||
password_reset:
|
password_reset:
|
||||||
|
## Disable both the HTML element and the API for reset password functionality.
|
||||||
|
disable: false
|
||||||
|
|
||||||
## External reset password url that redirects the user to an external reset portal. This disables the internal reset
|
## External reset password url that redirects the user to an external reset portal. This disables the internal reset
|
||||||
## functionality.
|
## functionality.
|
||||||
|
|
|
@ -18,7 +18,6 @@ aliases:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
authentication_backend:
|
authentication_backend:
|
||||||
disable_reset_password: false
|
|
||||||
file:
|
file:
|
||||||
path: /config/users.yml
|
path: /config/users.yml
|
||||||
password:
|
password:
|
||||||
|
|
|
@ -26,8 +26,8 @@ There are two ways to integrate *Authelia* with an authentication backend:
|
||||||
```yaml
|
```yaml
|
||||||
authentication_backend:
|
authentication_backend:
|
||||||
refresh_interval: 5m
|
refresh_interval: 5m
|
||||||
disable_reset_password: false
|
|
||||||
password_reset:
|
password_reset:
|
||||||
|
disable: false
|
||||||
custom_url: ""
|
custom_url: ""
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -40,14 +40,14 @@ authentication_backend:
|
||||||
This setting controls the interval at which details are refreshed from the backend. Particularly useful for
|
This setting controls the interval at which details are refreshed from the backend. Particularly useful for
|
||||||
[LDAP](#ldap).
|
[LDAP](#ldap).
|
||||||
|
|
||||||
### disable_reset_password
|
### password_reset
|
||||||
|
|
||||||
|
#### disable
|
||||||
|
|
||||||
{{< confkey type="boolean" default="false" required="no" >}}
|
{{< confkey type="boolean" default="false" required="no" >}}
|
||||||
|
|
||||||
This setting controls if users can reset their password from the web frontend or not.
|
This setting controls if users can reset their password from the web frontend or not.
|
||||||
|
|
||||||
### password_reset
|
|
||||||
|
|
||||||
#### custom_url
|
#### custom_url
|
||||||
|
|
||||||
{{< confkey type="string" required="no" >}}
|
{{< confkey type="string" required="no" >}}
|
||||||
|
|
|
@ -196,8 +196,7 @@ referrals to be followed when performing write operations.
|
||||||
server and utilizing a service account.*
|
server and utilizing a service account.*
|
||||||
|
|
||||||
Permits binding to the server without a password. For this option to be enabled both the [password](#password)
|
Permits binding to the server without a password. For this option to be enabled both the [password](#password)
|
||||||
configuration option must be blank and [disable_reset_password](introduction.md#disable_reset_password) must be
|
configuration option must be blank and the [password_reset disable](introduction.md#disable) option must be `true`.
|
||||||
disabled.
|
|
||||||
|
|
||||||
### user
|
### user
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,12 @@ aliases:
|
||||||
- /docs/configuration/migration.html
|
- /docs/configuration/migration.html
|
||||||
---
|
---
|
||||||
|
|
||||||
This section documents changes in the configuration which may require manual migration by the administrator. Typically
|
This section discusses the change to the configuration over time. Since v4.36.0 the migration process is automatically
|
||||||
this only occurs when a configuration key is renamed or moved to a more appropriate location.
|
performed where possible in memory (the file is unchanged). The automatic process generates warnings and the automatic
|
||||||
|
migrations are disabled in major version bumps.
|
||||||
|
|
||||||
|
If you're running a version prior to v4.36.0 this it may require manual migration by the administrator. Typically this
|
||||||
|
only occurs when a configuration key is renamed or moved to a more appropriate location.
|
||||||
|
|
||||||
## Format
|
## Format
|
||||||
|
|
||||||
|
@ -29,14 +33,18 @@ server:
|
||||||
host: 0.0.0.0
|
host: 0.0.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Policy
|
|
||||||
|
|
||||||
Our deprecation policy for configuration keys is 3 minor versions. For example if a configuration option is deprecated
|
|
||||||
in version 4.30.0, it will remain as a warning for 4.30.x, 4.31.x, and 4.32.x; then it will become a fatal error in
|
|
||||||
4.33.0+.
|
|
||||||
|
|
||||||
## Migrations
|
## Migrations
|
||||||
|
|
||||||
|
### 4.36.0
|
||||||
|
|
||||||
|
Automatic mapping was introduced in this version.
|
||||||
|
|
||||||
|
The following changes occurred in 4.30.0:
|
||||||
|
|
||||||
|
| Previous Key | New Key |
|
||||||
|
|:---------------------------------------------:|:---------------------------------------------:|
|
||||||
|
| authentication_backend.disable_reset_password | authentication_backend.password_reset.disable |
|
||||||
|
|
||||||
### 4.33.0
|
### 4.33.0
|
||||||
|
|
||||||
The options deprecated in version [4.30.0](#4300) have been fully removed as per our deprecation policy and warnings
|
The options deprecated in version [4.30.0](#4300) have been fully removed as per our deprecation policy and warnings
|
||||||
|
|
|
@ -224,7 +224,7 @@ To configure mutual TLS, please refer to [this document](../../configuration/mis
|
||||||
### Reset Password
|
### Reset Password
|
||||||
|
|
||||||
It's possible to disable the reset password functionality and is an optional adjustment to consider for anyone wanting
|
It's possible to disable the reset password functionality and is an optional adjustment to consider for anyone wanting
|
||||||
to increase security. See the [configuration](../../configuration/first-factor/introduction.md#disable_reset_password)
|
to increase security. See the [configuration](../../configuration/first-factor/introduction.md#disable)
|
||||||
for more information.
|
for more information.
|
||||||
|
|
||||||
### Session security
|
### Session security
|
||||||
|
|
|
@ -43,7 +43,7 @@ type LDAPUserProvider struct {
|
||||||
|
|
||||||
// NewLDAPUserProvider creates a new instance of LDAPUserProvider.
|
// NewLDAPUserProvider creates a new instance of LDAPUserProvider.
|
||||||
func NewLDAPUserProvider(config schema.AuthenticationBackendConfiguration, certPool *x509.CertPool) (provider *LDAPUserProvider) {
|
func NewLDAPUserProvider(config schema.AuthenticationBackendConfiguration, certPool *x509.CertPool) (provider *LDAPUserProvider) {
|
||||||
provider = newLDAPUserProvider(*config.LDAP, config.DisableResetPassword, certPool, nil)
|
provider = newLDAPUserProvider(*config.LDAP, config.PasswordReset.Disable, certPool, nil)
|
||||||
|
|
||||||
return provider
|
return provider
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,11 +216,10 @@ ntp:
|
||||||
##
|
##
|
||||||
## The available providers are: `file`, `ldap`. You must use only one of these providers.
|
## The available providers are: `file`, `ldap`. You must use only one of these providers.
|
||||||
authentication_backend:
|
authentication_backend:
|
||||||
## Disable both the HTML element and the API for reset password functionality.
|
|
||||||
disable_reset_password: false
|
|
||||||
|
|
||||||
## Password Reset Options.
|
## Password Reset Options.
|
||||||
password_reset:
|
password_reset:
|
||||||
|
## Disable both the HTML element and the API for reset password functionality.
|
||||||
|
disable: false
|
||||||
|
|
||||||
## External reset password url that redirects the user to an external reset portal. This disables the internal reset
|
## External reset password url that redirects the user to an external reset portal. This disables the internal reset
|
||||||
## functionality.
|
## functionality.
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/authelia/authelia/v4/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecation represents a deprecated configuration key.
|
||||||
|
type Deprecation struct {
|
||||||
|
Version model.SemanticVersion
|
||||||
|
Key string
|
||||||
|
NewKey string
|
||||||
|
AutoMap bool
|
||||||
|
MapFunc func(value interface{}) interface{}
|
||||||
|
ErrText string
|
||||||
|
}
|
||||||
|
|
||||||
|
var deprecations = map[string]Deprecation{
|
||||||
|
"logs_level": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 7},
|
||||||
|
Key: "logs_level",
|
||||||
|
NewKey: "log.level",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"logs_file": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 7},
|
||||||
|
Key: "logs_file",
|
||||||
|
NewKey: "log.file_path",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"authentication_backend.ldap.skip_verify": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 25},
|
||||||
|
Key: "authentication_backend.ldap.skip_verify",
|
||||||
|
NewKey: "authentication_backend.ldap.tls.skip_verify",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"authentication_backend.ldap.minimum_tls_version": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 25},
|
||||||
|
Key: "authentication_backend.ldap.minimum_tls_version",
|
||||||
|
NewKey: "authentication_backend.ldap.tls.minimum_version",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"notifier.smtp.disable_verify_cert": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 25},
|
||||||
|
Key: "notifier.smtp.disable_verify_cert",
|
||||||
|
NewKey: "notifier.smtp.tls.skip_verify",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"notifier.smtp.trusted_cert": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 25},
|
||||||
|
Key: "notifier.smtp.trusted_cert",
|
||||||
|
NewKey: "certificates_directory",
|
||||||
|
AutoMap: false,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"host": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 30},
|
||||||
|
Key: "logs_file",
|
||||||
|
NewKey: "server.host",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 30},
|
||||||
|
Key: "port",
|
||||||
|
NewKey: "server.port",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"tls_key": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 30},
|
||||||
|
Key: "tls_key",
|
||||||
|
NewKey: "server.tls.key",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"tls_cert": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 30},
|
||||||
|
Key: "tls_cert",
|
||||||
|
NewKey: "server.tls.certificate",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"log_level": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 30},
|
||||||
|
Key: "log_level",
|
||||||
|
NewKey: "log.level",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"log_file_path": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 30},
|
||||||
|
Key: "log_file_path",
|
||||||
|
NewKey: "log.file_path",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"log_format": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 30},
|
||||||
|
Key: "log_format",
|
||||||
|
NewKey: "log.format",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
"authentication_backend.disable_reset_password": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 36},
|
||||||
|
Key: "authentication_backend.disable_reset_password",
|
||||||
|
NewKey: "authentication_backend.password_reset.disable",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
|
}
|
|
@ -2,13 +2,16 @@ package configuration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/knadh/koanf"
|
"github.com/knadh/koanf"
|
||||||
|
"github.com/knadh/koanf/providers/confmap"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getAllKoanfKeys(ko *koanf.Koanf) (keys []string) {
|
func koanfGetKeys(ko *koanf.Koanf) (keys []string) {
|
||||||
keys = ko.Keys()
|
keys = ko.Keys()
|
||||||
|
|
||||||
for key, value := range ko.All() {
|
for key, value := range ko.All() {
|
||||||
|
@ -34,3 +37,128 @@ func getAllKoanfKeys(ko *koanf.Koanf) (keys []string) {
|
||||||
|
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func koanfRemapKeys(val *schema.StructValidator, ko *koanf.Koanf, ds map[string]Deprecation) (final *koanf.Koanf, err error) {
|
||||||
|
keys := ko.All()
|
||||||
|
|
||||||
|
keys = koanfRemapKeysStandard(keys, val, ds)
|
||||||
|
keys = koanfRemapKeysMapped(keys, val, ds)
|
||||||
|
|
||||||
|
final = koanf.New(".")
|
||||||
|
|
||||||
|
if err = final.Load(confmap.Provider(keys, "."), nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return final, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func koanfRemapKeysStandard(keys map[string]interface{}, val *schema.StructValidator, ds map[string]Deprecation) (keysFinal map[string]interface{}) {
|
||||||
|
var (
|
||||||
|
ok bool
|
||||||
|
d Deprecation
|
||||||
|
key string
|
||||||
|
value interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
keysFinal = make(map[string]interface{})
|
||||||
|
|
||||||
|
for key, value = range keys {
|
||||||
|
if d, ok = ds[key]; ok {
|
||||||
|
if !d.AutoMap {
|
||||||
|
val.Push(fmt.Errorf("invalid configuration key '%s' was replaced by '%s'", d.Key, d.NewKey))
|
||||||
|
|
||||||
|
keysFinal[key] = value
|
||||||
|
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
val.PushWarning(fmt.Errorf("configuration key '%s' is deprecated in %s and has been replaced by '%s': "+
|
||||||
|
"this has been automatically mapped for you but you will need to adjust your configuration to remove this message", d.Key, d.Version.String(), d.NewKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mapHasKey(d.NewKey, keys) && !mapHasKey(d.NewKey, keysFinal) {
|
||||||
|
if d.MapFunc != nil {
|
||||||
|
keysFinal[d.NewKey] = d.MapFunc(value)
|
||||||
|
} else {
|
||||||
|
keysFinal[d.NewKey] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
keysFinal[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return keysFinal
|
||||||
|
}
|
||||||
|
|
||||||
|
func koanfRemapKeysMapped(keys map[string]interface{}, val *schema.StructValidator, ds map[string]Deprecation) (keysFinal map[string]interface{}) {
|
||||||
|
var (
|
||||||
|
key string
|
||||||
|
value interface{}
|
||||||
|
slc, slcFinal []interface{}
|
||||||
|
ok bool
|
||||||
|
m map[string]interface{}
|
||||||
|
d Deprecation
|
||||||
|
)
|
||||||
|
|
||||||
|
keysFinal = make(map[string]interface{})
|
||||||
|
|
||||||
|
for key, value = range keys {
|
||||||
|
if slc, ok = value.([]interface{}); !ok {
|
||||||
|
keysFinal[key] = value
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
slcFinal = make([]interface{}, len(slc))
|
||||||
|
|
||||||
|
for i, item := range slc {
|
||||||
|
if m, ok = item.(map[string]interface{}); !ok {
|
||||||
|
slcFinal[i] = item
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
itemFinal := make(map[string]interface{})
|
||||||
|
|
||||||
|
for subkey, element := range m {
|
||||||
|
prefix := fmt.Sprintf("%s[].", key)
|
||||||
|
|
||||||
|
fullKey := prefix + subkey
|
||||||
|
|
||||||
|
if d, ok = ds[fullKey]; ok {
|
||||||
|
if !d.AutoMap {
|
||||||
|
val.Push(fmt.Errorf("invalid configuration key '%s' was replaced by '%s'", d.Key, d.NewKey))
|
||||||
|
|
||||||
|
itemFinal[subkey] = element
|
||||||
|
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
val.PushWarning(fmt.Errorf("configuration key '%s' is deprecated in %s and has been replaced by '%s': "+
|
||||||
|
"this has been automatically mapped for you but you will need to adjust your configuration to remove this message", d.Key, d.Version.String(), d.NewKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
newkey := strings.Replace(d.NewKey, prefix, "", 1)
|
||||||
|
|
||||||
|
if !mapHasKey(newkey, m) && !mapHasKey(newkey, itemFinal) {
|
||||||
|
if d.MapFunc != nil {
|
||||||
|
itemFinal[newkey] = d.MapFunc(element)
|
||||||
|
} else {
|
||||||
|
itemFinal[newkey] = element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
itemFinal[subkey] = element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slcFinal[i] = itemFinal
|
||||||
|
}
|
||||||
|
|
||||||
|
keysFinal[key] = slcFinal
|
||||||
|
}
|
||||||
|
|
||||||
|
return keysFinal
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/knadh/koanf"
|
||||||
|
"github.com/knadh/koanf/parsers/yaml"
|
||||||
|
"github.com/knadh/koanf/providers/rawbytes"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/v4/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testDeprecationsConf struct {
|
||||||
|
SubItems []testDeprecationsConfSubItem `koanf:"subitems"`
|
||||||
|
|
||||||
|
ANonSubItemString string `koanf:"a_non_subitem_string"`
|
||||||
|
ANonSubItemInt int `koanf:"a_non_subitem_int"`
|
||||||
|
ANonSubItemBool bool `koanf:"a_non_subitem_bool"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testDeprecationsConfSubItem struct {
|
||||||
|
AString string `koanf:"a_string"`
|
||||||
|
AnInt int `koanf:"an_int"`
|
||||||
|
ABool bool `koanf:"a_bool"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubItemRemap(t *testing.T) {
|
||||||
|
ds := map[string]Deprecation{
|
||||||
|
"astring": {
|
||||||
|
Key: "astring",
|
||||||
|
NewKey: "a_non_subitem_string",
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 30},
|
||||||
|
AutoMap: true,
|
||||||
|
},
|
||||||
|
"subitems[].astring": {
|
||||||
|
Key: "subitems[].astring",
|
||||||
|
NewKey: "subitems[].a_string",
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 30},
|
||||||
|
AutoMap: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
val := schema.NewStructValidator()
|
||||||
|
|
||||||
|
ko := koanf.New(".")
|
||||||
|
|
||||||
|
configYAML := []byte(`
|
||||||
|
astring: test
|
||||||
|
subitems:
|
||||||
|
- astring: example
|
||||||
|
- an_int: 1
|
||||||
|
`)
|
||||||
|
|
||||||
|
require.NoError(t, ko.Load(rawbytes.Provider(configYAML), yaml.Parser()))
|
||||||
|
|
||||||
|
final, err := koanfRemapKeys(val, ko, ds)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
conf := &testDeprecationsConf{}
|
||||||
|
|
||||||
|
require.NoError(t, final.Unmarshal("", conf))
|
||||||
|
|
||||||
|
assert.Equal(t, "test", conf.ANonSubItemString)
|
||||||
|
assert.Equal(t, 0, conf.ANonSubItemInt)
|
||||||
|
assert.False(t, conf.ANonSubItemBool)
|
||||||
|
|
||||||
|
require.Len(t, conf.SubItems, 2)
|
||||||
|
assert.Equal(t, "example", conf.SubItems[0].AString)
|
||||||
|
assert.Equal(t, 0, conf.SubItems[0].AnInt)
|
||||||
|
assert.Equal(t, "", conf.SubItems[1].AString)
|
||||||
|
assert.Equal(t, 1, conf.SubItems[1].AnInt)
|
||||||
|
}
|
|
@ -29,14 +29,27 @@ func LoadAdvanced(val *schema.StructValidator, path string, result interface{},
|
||||||
StrictMerge: false,
|
StrictMerge: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
err = loadSources(ko, val, sources...)
|
if err = loadSources(ko, val, sources...); err != nil {
|
||||||
if err != nil {
|
|
||||||
return ko.Keys(), err
|
return ko.Keys(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
unmarshal(ko, val, path, result)
|
var final *koanf.Koanf
|
||||||
|
|
||||||
return getAllKoanfKeys(ko), nil
|
if final, err = koanfRemapKeys(val, ko, deprecations); err != nil {
|
||||||
|
return koanfGetKeys(ko), err
|
||||||
|
}
|
||||||
|
|
||||||
|
unmarshal(final, val, path, result)
|
||||||
|
|
||||||
|
return koanfGetKeys(final), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapHasKey(k string, m map[string]interface{}) bool {
|
||||||
|
if _, ok := m[k]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o interface{}) {
|
func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o interface{}) {
|
||||||
|
|
|
@ -228,17 +228,19 @@ func TestShouldValidateAndRaiseErrorsOnBadConfiguration(t *testing.T) {
|
||||||
testSetEnv(t, "AUTHENTICATION_BACKEND_LDAP_PASSWORD", "abc")
|
testSetEnv(t, "AUTHENTICATION_BACKEND_LDAP_PASSWORD", "abc")
|
||||||
|
|
||||||
val := schema.NewStructValidator()
|
val := schema.NewStructValidator()
|
||||||
keys, _, err := Load(val, NewDefaultSources([]string{"./test_resources/config_bad_keys.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
keys, c, err := Load(val, NewDefaultSources([]string{"./test_resources/config_bad_keys.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
validator.ValidateKeys(keys, DefaultEnvPrefix, val)
|
validator.ValidateKeys(keys, DefaultEnvPrefix, val)
|
||||||
|
|
||||||
require.Len(t, val.Errors(), 2)
|
require.Len(t, val.Errors(), 1)
|
||||||
assert.Len(t, val.Warnings(), 0)
|
require.Len(t, val.Warnings(), 1)
|
||||||
|
|
||||||
assert.EqualError(t, val.Errors()[0], "configuration key not expected: loggy_file")
|
assert.EqualError(t, val.Errors()[0], "configuration key not expected: loggy_file")
|
||||||
assert.EqualError(t, val.Errors()[1], "invalid configuration key 'logs_level' was replaced by 'log.level'")
|
assert.EqualError(t, val.Warnings()[0], "configuration key 'logs_level' is deprecated in 4.7.0 and has been replaced by 'log.level': this has been automatically mapped for you but you will need to adjust your configuration to remove this message")
|
||||||
|
|
||||||
|
assert.Equal(t, "debug", c.Log.Level)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseErrOnInvalidNotifierSMTPSender(t *testing.T) {
|
func TestShouldRaiseErrOnInvalidNotifierSMTPSender(t *testing.T) {
|
||||||
|
|
|
@ -56,12 +56,12 @@ type AuthenticationBackendConfiguration struct {
|
||||||
|
|
||||||
PasswordReset PasswordResetAuthenticationBackendConfiguration `koanf:"password_reset"`
|
PasswordReset PasswordResetAuthenticationBackendConfiguration `koanf:"password_reset"`
|
||||||
|
|
||||||
DisableResetPassword bool `koanf:"disable_reset_password"`
|
RefreshInterval string `koanf:"refresh_interval"`
|
||||||
RefreshInterval string `koanf:"refresh_interval"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordResetAuthenticationBackendConfiguration represents the configuration related to password reset functionality.
|
// PasswordResetAuthenticationBackendConfiguration represents the configuration related to password reset functionality.
|
||||||
type PasswordResetAuthenticationBackendConfiguration struct {
|
type PasswordResetAuthenticationBackendConfiguration struct {
|
||||||
|
Disable bool `koanf:"disable"`
|
||||||
CustomURL url.URL `koanf:"custom_url"`
|
CustomURL url.URL `koanf:"custom_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,7 @@ func EnsureConfigurationExists(path string) (created bool, err error) {
|
||||||
_, err = os.Stat(path)
|
_, err = os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
err := os.WriteFile(path, template, 0600)
|
if err = os.WriteFile(path, template, 0600); err != nil {
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf(errFmtGenerateConfiguration, err)
|
return false, fmt.Errorf(errFmtGenerateConfiguration, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ func ValidateAuthenticationBackend(config *schema.AuthenticationBackendConfigura
|
||||||
if config.PasswordReset.CustomURL.String() != "" {
|
if config.PasswordReset.CustomURL.String() != "" {
|
||||||
switch config.PasswordReset.CustomURL.Scheme {
|
switch config.PasswordReset.CustomURL.Scheme {
|
||||||
case schemeHTTP, schemeHTTPS:
|
case schemeHTTP, schemeHTTPS:
|
||||||
config.DisableResetPassword = false
|
config.PasswordReset.Disable = false
|
||||||
default:
|
default:
|
||||||
validator.Push(fmt.Errorf(errFmtAuthBackendPasswordResetCustomURLScheme, config.PasswordReset.CustomURL.String(), config.PasswordReset.CustomURL.Scheme))
|
validator.Push(fmt.Errorf(errFmtAuthBackendPasswordResetCustomURLScheme, config.PasswordReset.CustomURL.String(), config.PasswordReset.CustomURL.Scheme))
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,7 @@ func validateLDAPRequiredParameters(config *schema.AuthenticationBackendConfigur
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendUnauthenticatedBindWithPassword))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendUnauthenticatedBindWithPassword))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.DisableResetPassword {
|
if !config.PasswordReset.Disable {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendUnauthenticatedBindWithResetEnabled))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendUnauthenticatedBindWithResetEnabled))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -233,9 +233,9 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateCompleteConfigura
|
||||||
|
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenResetURLIsInvalid() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenResetURLIsInvalid() {
|
||||||
suite.config.PasswordReset.CustomURL = url.URL{Scheme: "ldap", Host: "google.com"}
|
suite.config.PasswordReset.CustomURL = url.URL{Scheme: "ldap", Host: "google.com"}
|
||||||
suite.config.DisableResetPassword = true
|
suite.config.PasswordReset.Disable = true
|
||||||
|
|
||||||
suite.Assert().True(suite.config.DisableResetPassword)
|
suite.Assert().True(suite.config.PasswordReset.Disable)
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenResetURLIsI
|
||||||
|
|
||||||
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: password_reset: option 'custom_url' is configured to 'ldap://google.com' which has the scheme 'ldap' but the scheme must be either 'http' or 'https'")
|
suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: password_reset: option 'custom_url' is configured to 'ldap://google.com' which has the scheme 'ldap' but the scheme must be either 'http' or 'https'")
|
||||||
|
|
||||||
suite.Assert().True(suite.config.DisableResetPassword)
|
suite.Assert().True(suite.config.PasswordReset.Disable)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldNotRaiseErrorWhenResetURLIsValid() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldNotRaiseErrorWhenResetURLIsValid() {
|
||||||
|
@ -258,16 +258,16 @@ func (suite *FileBasedAuthenticationBackend) TestShouldNotRaiseErrorWhenResetURL
|
||||||
|
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldConfigureDisableResetPasswordWhenCustomURL() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldConfigureDisableResetPasswordWhenCustomURL() {
|
||||||
suite.config.PasswordReset.CustomURL = url.URL{Scheme: "https", Host: "google.com"}
|
suite.config.PasswordReset.CustomURL = url.URL{Scheme: "https", Host: "google.com"}
|
||||||
suite.config.DisableResetPassword = true
|
suite.config.PasswordReset.Disable = true
|
||||||
|
|
||||||
suite.Assert().True(suite.config.DisableResetPassword)
|
suite.Assert().True(suite.config.PasswordReset.Disable)
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
ValidateAuthenticationBackend(&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().False(suite.config.DisableResetPassword)
|
suite.Assert().False(suite.config.PasswordReset.Disable)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errFmtValueNil = "cannot value model type '%T' with value nil to driver.Value"
|
errFmtValueNil = "cannot value model type '%T' with value nil to driver.Value"
|
||||||
errFmtScanNil = "cannot scan model type '%T' from value nil: type doesn't support nil values"
|
errFmtScanNil = "cannot scan model type '%T' from value nil: type doesn't support nil values"
|
||||||
|
@ -17,3 +21,9 @@ const (
|
||||||
// SecondFactorMethodDuo method using Duo application to receive push notifications.
|
// SecondFactorMethodDuo method using Duo application to receive push notifications.
|
||||||
SecondFactorMethodDuo = "mobile_push"
|
SecondFactorMethodDuo = "mobile_push"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var reSemanticVersion = regexp.MustCompile(`^v?(?P<Major>\d+)\.(?P<Minor>\d+)\.(?P<Patch>\d+)(\-(?P<PreRelease>[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*))?(\+(?P<Metadata>[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*))?$`)
|
||||||
|
|
||||||
|
const (
|
||||||
|
semverRegexpGroupPreRelease = "PreRelease"
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSemanticVersion creates a SemanticVersion from a string.
|
||||||
|
func NewSemanticVersion(input string) (version *SemanticVersion, err error) {
|
||||||
|
if !reSemanticVersion.MatchString(input) {
|
||||||
|
return nil, fmt.Errorf("the input '%s' failed to match the semantic version pattern", input)
|
||||||
|
}
|
||||||
|
|
||||||
|
version = &SemanticVersion{}
|
||||||
|
|
||||||
|
submatch := reSemanticVersion.FindStringSubmatch(input)
|
||||||
|
|
||||||
|
for i, name := range reSemanticVersion.SubexpNames() {
|
||||||
|
switch name {
|
||||||
|
case "Major":
|
||||||
|
version.Major, _ = strconv.Atoi(submatch[i])
|
||||||
|
case "Minor":
|
||||||
|
version.Minor, _ = strconv.Atoi(submatch[i])
|
||||||
|
case "Patch":
|
||||||
|
version.Patch, _ = strconv.Atoi(submatch[i])
|
||||||
|
case semverRegexpGroupPreRelease, "Metadata":
|
||||||
|
if submatch[i] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val := strings.Split(submatch[i], ".")
|
||||||
|
|
||||||
|
if name == semverRegexpGroupPreRelease {
|
||||||
|
version.PreRelease = val
|
||||||
|
} else {
|
||||||
|
version.Metadata = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SemanticVersion represents a semantic 2.0 version.
|
||||||
|
type SemanticVersion struct {
|
||||||
|
Major int
|
||||||
|
Minor int
|
||||||
|
Patch int
|
||||||
|
PreRelease []string
|
||||||
|
Metadata []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is a function to provide a nice representation of a SemanticVersion.
|
||||||
|
func (v SemanticVersion) String() (value string) {
|
||||||
|
builder := strings.Builder{}
|
||||||
|
|
||||||
|
builder.WriteString(fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch))
|
||||||
|
|
||||||
|
if len(v.PreRelease) != 0 {
|
||||||
|
builder.WriteString("-")
|
||||||
|
builder.WriteString(strings.Join(v.PreRelease, "."))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.Metadata) != 0 {
|
||||||
|
builder.WriteString("+")
|
||||||
|
builder.WriteString(strings.Join(v.Metadata, "."))
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if this SemanticVersion is equal to the provided SemanticVersion.
|
||||||
|
func (v SemanticVersion) Equal(version SemanticVersion) (equals bool) {
|
||||||
|
return v.Major == version.Major && v.Minor == version.Minor && v.Patch == version.Patch
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterThan returns true if this SemanticVersion is greater than the provided SemanticVersion.
|
||||||
|
func (v SemanticVersion) GreaterThan(version SemanticVersion) (gt bool) {
|
||||||
|
if v.Major > version.Major {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Major == version.Major && v.Minor > version.Minor {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Major == version.Major && v.Minor == version.Minor && v.Patch > version.Patch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// LessThan returns true if this SemanticVersion is less than the provided SemanticVersion.
|
||||||
|
func (v SemanticVersion) LessThan(version SemanticVersion) (gt bool) {
|
||||||
|
if v.Major < version.Major {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Major == version.Major && v.Minor < version.Minor {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Major == version.Major && v.Minor == version.Minor && v.Patch < version.Patch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterThanOrEqual returns true if this SemanticVersion is greater than or equal to the provided SemanticVersion.
|
||||||
|
func (v SemanticVersion) GreaterThanOrEqual(version SemanticVersion) (ge bool) {
|
||||||
|
return v.Equal(version) || v.GreaterThan(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LessThanOrEqual returns true if this SemanticVersion is less than or equal to the provided SemanticVersion.
|
||||||
|
func (v SemanticVersion) LessThanOrEqual(version SemanticVersion) (ge bool) {
|
||||||
|
return v.Equal(version) || v.LessThan(version)
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewSemanticVersion(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
have string
|
||||||
|
expected *SemanticVersion
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "ShouldParseStandardSemVer",
|
||||||
|
have: "4.30.0",
|
||||||
|
expected: &SemanticVersion{Major: 4, Minor: 30, Patch: 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldParseSemVerWithPre",
|
||||||
|
have: "4.30.0-alpha1",
|
||||||
|
expected: &SemanticVersion{Major: 4, Minor: 30, Patch: 0, PreRelease: []string{"alpha1"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldParseSemVerWithMeta",
|
||||||
|
have: "4.30.0+build4",
|
||||||
|
expected: &SemanticVersion{Major: 4, Minor: 30, Patch: 0, Metadata: []string{"build4"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldParseSemVerWithPreAndMeta",
|
||||||
|
have: "4.30.0-alpha1+build4",
|
||||||
|
expected: &SemanticVersion{Major: 4, Minor: 30, Patch: 0, PreRelease: []string{"alpha1"}, Metadata: []string{"build4"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldParseSemVerWithPreAndMetaMulti",
|
||||||
|
have: "4.30.0-alpha1.test+build4.new",
|
||||||
|
expected: &SemanticVersion{Major: 4, Minor: 30, Patch: 0, PreRelease: []string{"alpha1", "test"}, Metadata: []string{"build4", "new"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldNotParseInvalidVersion",
|
||||||
|
have: "1.2",
|
||||||
|
expected: nil,
|
||||||
|
err: "the input '1.2' failed to match the semantic version pattern",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
version, err := NewSemanticVersion(tc.have)
|
||||||
|
|
||||||
|
if tc.err == "" {
|
||||||
|
assert.Nil(t, err)
|
||||||
|
require.NotNil(t, version)
|
||||||
|
assert.Equal(t, tc.expected, version)
|
||||||
|
assert.Equal(t, tc.have, version.String())
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, version)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.EqualError(t, err, tc.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSemanticVersionComparisons(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
|
||||||
|
haveFirst, haveSecond SemanticVersion
|
||||||
|
|
||||||
|
expectedEQ, expectedGT, expectedGE, expectedLT, expectedLE bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "ShouldCompareVersionLessThanMajor",
|
||||||
|
haveFirst: SemanticVersion{Major: 4, Minor: 30, Patch: 0},
|
||||||
|
haveSecond: SemanticVersion{Major: 5, Minor: 3, Patch: 0},
|
||||||
|
expectedEQ: false,
|
||||||
|
expectedGT: false,
|
||||||
|
expectedGE: false,
|
||||||
|
expectedLT: true,
|
||||||
|
expectedLE: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldCompareVersionLessThanMinor",
|
||||||
|
haveFirst: SemanticVersion{Major: 4, Minor: 30, Patch: 0},
|
||||||
|
haveSecond: SemanticVersion{Major: 4, Minor: 31, Patch: 0},
|
||||||
|
expectedEQ: false,
|
||||||
|
expectedGT: false,
|
||||||
|
expectedGE: false,
|
||||||
|
expectedLT: true,
|
||||||
|
expectedLE: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldCompareVersionLessThanPatch",
|
||||||
|
haveFirst: SemanticVersion{Major: 4, Minor: 31, Patch: 0},
|
||||||
|
haveSecond: SemanticVersion{Major: 4, Minor: 31, Patch: 9},
|
||||||
|
expectedEQ: false,
|
||||||
|
expectedGT: false,
|
||||||
|
expectedGE: false,
|
||||||
|
expectedLT: true,
|
||||||
|
expectedLE: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldCompareVersionEqual",
|
||||||
|
haveFirst: SemanticVersion{Major: 4, Minor: 31, Patch: 0},
|
||||||
|
haveSecond: SemanticVersion{Major: 4, Minor: 31, Patch: 0},
|
||||||
|
expectedEQ: true,
|
||||||
|
expectedGT: false,
|
||||||
|
expectedGE: true,
|
||||||
|
expectedLT: false,
|
||||||
|
expectedLE: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldCompareVersionGreaterThanMajor",
|
||||||
|
haveFirst: SemanticVersion{Major: 5, Minor: 0, Patch: 0},
|
||||||
|
haveSecond: SemanticVersion{Major: 4, Minor: 30, Patch: 0},
|
||||||
|
expectedEQ: false,
|
||||||
|
expectedGT: true,
|
||||||
|
expectedGE: true,
|
||||||
|
expectedLT: false,
|
||||||
|
expectedLE: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldCompareVersionGreaterThanMinor",
|
||||||
|
haveFirst: SemanticVersion{Major: 4, Minor: 31, Patch: 0},
|
||||||
|
haveSecond: SemanticVersion{Major: 4, Minor: 30, Patch: 0},
|
||||||
|
expectedEQ: false,
|
||||||
|
expectedGT: true,
|
||||||
|
expectedGE: true,
|
||||||
|
expectedLT: false,
|
||||||
|
expectedLE: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldCompareVersionGreaterThanPatch",
|
||||||
|
haveFirst: SemanticVersion{Major: 4, Minor: 31, Patch: 5},
|
||||||
|
haveSecond: SemanticVersion{Major: 4, Minor: 31, Patch: 0},
|
||||||
|
expectedEQ: false,
|
||||||
|
expectedGT: true,
|
||||||
|
expectedGE: true,
|
||||||
|
expectedLT: false,
|
||||||
|
expectedLE: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expectedEQ, tc.haveFirst.Equal(tc.haveSecond))
|
||||||
|
assert.Equal(t, tc.expectedGT, tc.haveFirst.GreaterThan(tc.haveSecond))
|
||||||
|
assert.Equal(t, tc.expectedGE, tc.haveFirst.GreaterThanOrEqual(tc.haveSecond))
|
||||||
|
assert.Equal(t, tc.expectedLT, tc.haveFirst.LessThan(tc.haveSecond))
|
||||||
|
assert.Equal(t, tc.expectedLE, tc.haveFirst.LessThanOrEqual(tc.haveSecond))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -93,7 +93,7 @@ func handleNotFound(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
|
|
||||||
func handleRouter(config schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler {
|
func handleRouter(config schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler {
|
||||||
rememberMe := strconv.FormatBool(config.Session.RememberMeDuration != schema.RememberMeDisabled)
|
rememberMe := strconv.FormatBool(config.Session.RememberMeDuration != schema.RememberMeDisabled)
|
||||||
resetPassword := strconv.FormatBool(!config.AuthenticationBackend.DisableResetPassword)
|
resetPassword := strconv.FormatBool(!config.AuthenticationBackend.PasswordReset.Disable)
|
||||||
|
|
||||||
resetPasswordCustomURL := config.AuthenticationBackend.PasswordReset.CustomURL.String()
|
resetPasswordCustomURL := config.AuthenticationBackend.PasswordReset.CustomURL.String()
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
||||||
r.POST("/api/logout", middlewareAPI(handlers.LogoutPOST))
|
r.POST("/api/logout", middlewareAPI(handlers.LogoutPOST))
|
||||||
|
|
||||||
// Only register endpoints if forgot password is not disabled.
|
// Only register endpoints if forgot password is not disabled.
|
||||||
if !config.AuthenticationBackend.DisableResetPassword &&
|
if !config.AuthenticationBackend.PasswordReset.Disable &&
|
||||||
config.AuthenticationBackend.PasswordReset.CustomURL.String() == "" {
|
config.AuthenticationBackend.PasswordReset.CustomURL.String() == "" {
|
||||||
// Password reset related endpoints.
|
// Password reset related endpoints.
|
||||||
r.POST("/api/reset-password/identity/start", middlewareAPI(handlers.ResetPasswordIdentityStart))
|
r.POST("/api/reset-password/identity/start", middlewareAPI(handlers.ResetPasswordIdentityStart))
|
||||||
|
|
Loading…
Reference in New Issue