diff --git a/config.template.yml b/config.template.yml
index 1739cec81..c4eaabfa1 100644
--- a/config.template.yml
+++ b/config.template.yml
@@ -158,6 +158,7 @@ webauthn:
## Parameters used to contact the Duo API. Those are generated when you protect an application of type
## "Partner Auth API" in the management panel.
duo_api:
+ disable: false
hostname: api-123456789.example.com
integration_key: ABCDEF
## Secret can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
diff --git a/docs/configuration/duo-push-notifications.md b/docs/configuration/duo-push-notifications.md
index b69c93960..26af6e27c 100644
--- a/docs/configuration/duo-push-notifications.md
+++ b/docs/configuration/duo-push-notifications.md
@@ -21,6 +21,7 @@ section of the configuration.
The configuration is as follows:
```yaml
duo_api:
+ disable: false
hostname: api-123456789.example.com
integration_key: ABCDEF
secret_key: 1234567890abcdefghifjkl
@@ -32,6 +33,19 @@ variable as described [here](./secrets.md).
## Options
+### disable:
+
+type: boolean
+{: .label .label-config .label-purple }
+default: false
+{: .label .label-config .label-blue }
+required: no
+{: .label .label-config .label-green }
+
+
+Disables Duo. If the hostname, integration_key, and secret_key are all empty strings or undefined this is automatically
+true.
+
### hostname
type: string
diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml
index 1739cec81..c4eaabfa1 100644
--- a/internal/configuration/config.template.yml
+++ b/internal/configuration/config.template.yml
@@ -158,6 +158,7 @@ webauthn:
## Parameters used to contact the Duo API. Those are generated when you protect an application of type
## "Partner Auth API" in the management panel.
duo_api:
+ disable: false
hostname: api-123456789.example.com
integration_key: ABCDEF
## Secret can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go
index 68c0a0b02..d3fd9cb5a 100644
--- a/internal/configuration/provider_test.go
+++ b/internal/configuration/provider_test.go
@@ -273,7 +273,7 @@ func TestShouldHandleErrInvalidatorWhenSMTPSenderBlank(t *testing.T) {
assert.Equal(t, "", config.Notifier.SMTP.Sender.Name)
assert.Equal(t, "", config.Notifier.SMTP.Sender.Address)
- validator.ValidateNotifier(config.Notifier, val)
+ validator.ValidateNotifier(&config.Notifier, val)
require.Len(t, val.Errors(), 1)
assert.Len(t, val.Warnings(), 0)
diff --git a/internal/configuration/schema/configuration.go b/internal/configuration/schema/configuration.go
index bcaf2f344..54cfeadd6 100644
--- a/internal/configuration/schema/configuration.go
+++ b/internal/configuration/schema/configuration.go
@@ -12,12 +12,12 @@ type Configuration struct {
AuthenticationBackend AuthenticationBackendConfiguration `koanf:"authentication_backend"`
Session SessionConfiguration `koanf:"session"`
TOTP TOTPConfiguration `koanf:"totp"`
- DuoAPI *DuoAPIConfiguration `koanf:"duo_api"`
+ DuoAPI DuoAPIConfiguration `koanf:"duo_api"`
AccessControl AccessControlConfiguration `koanf:"access_control"`
NTP NTPConfiguration `koanf:"ntp"`
Regulation RegulationConfiguration `koanf:"regulation"`
Storage StorageConfiguration `koanf:"storage"`
- Notifier *NotifierConfiguration `koanf:"notifier"`
+ Notifier NotifierConfiguration `koanf:"notifier"`
Server ServerConfiguration `koanf:"server"`
Webauthn WebauthnConfiguration `koanf:"webauthn"`
PasswordPolicy PasswordPolicyConfiguration `koanf:"password_policy"`
diff --git a/internal/configuration/schema/duo.go b/internal/configuration/schema/duo.go
index 55e81d217..75e383d3e 100644
--- a/internal/configuration/schema/duo.go
+++ b/internal/configuration/schema/duo.go
@@ -2,8 +2,9 @@ package schema
// DuoAPIConfiguration represents the configuration related to Duo API.
type DuoAPIConfiguration struct {
+ Disable bool `koanf:"disable"`
Hostname string `koanf:"hostname"`
- EnableSelfEnrollment bool `koanf:"enable_self_enrollment"`
IntegrationKey string `koanf:"integration_key"`
SecretKey string `koanf:"secret_key"`
+ EnableSelfEnrollment bool `koanf:"enable_self_enrollment"`
}
diff --git a/internal/configuration/validator/configuration.go b/internal/configuration/validator/configuration.go
index ae34d6b74..ef8b05086 100644
--- a/internal/configuration/validator/configuration.go
+++ b/internal/configuration/validator/configuration.go
@@ -37,6 +37,8 @@ func ValidateConfiguration(config *schema.Configuration, validator *schema.Struc
ValidateLog(config, validator)
+ ValidateDuo(config, validator)
+
ValidateTOTP(config, validator)
ValidateWebauthn(config, validator)
@@ -55,7 +57,7 @@ func ValidateConfiguration(config *schema.Configuration, validator *schema.Struc
ValidateStorage(config.Storage, validator)
- ValidateNotifier(config.Notifier, validator)
+ ValidateNotifier(&config.Notifier, validator)
ValidateIdentityProviders(&config.IdentityProviders, validator)
diff --git a/internal/configuration/validator/configuration_test.go b/internal/configuration/validator/configuration_test.go
index 8cff7dc52..00dc7e519 100644
--- a/internal/configuration/validator/configuration_test.go
+++ b/internal/configuration/validator/configuration_test.go
@@ -32,7 +32,7 @@ func newDefaultConfig() schema.Configuration {
config.Storage.Local = &schema.LocalStorageConfiguration{
Path: "abc",
}
- config.Notifier = &schema.NotifierConfiguration{
+ config.Notifier = schema.NotifierConfiguration{
FileSystem: &schema.FileSystemNotifierConfiguration{
Filename: "/tmp/file",
},
@@ -48,7 +48,8 @@ func TestShouldEnsureNotifierConfigIsProvided(t *testing.T) {
ValidateConfiguration(&config, validator)
require.Len(t, validator.Errors(), 0)
- config.Notifier = nil
+ config.Notifier.SMTP = nil
+ config.Notifier.FileSystem = nil
ValidateConfiguration(&config, validator)
require.Len(t, validator.Errors(), 1)
diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go
index 9eb02cac0..14617ecac 100644
--- a/internal/configuration/validator/const.go
+++ b/internal/configuration/validator/const.go
@@ -249,6 +249,10 @@ const (
errFmtPasswordPolicyZXCVBNMinScoreInvalid = "password_policy: zxcvbn: option 'min_score' is invalid: must be between 1 and 4 but it's configured as %d"
)
+const (
+ errFmtDuoMissingOption = "duo_api: option '%s' is required when duo is enabled but it is missing"
+)
+
// Error constants.
const (
/*
diff --git a/internal/configuration/validator/duo.go b/internal/configuration/validator/duo.go
new file mode 100644
index 000000000..5d6938b6d
--- /dev/null
+++ b/internal/configuration/validator/duo.go
@@ -0,0 +1,34 @@
+package validator
+
+import (
+ "fmt"
+
+ "github.com/authelia/authelia/v4/internal/configuration/schema"
+)
+
+// ValidateDuo validates and updates the Duo configuration.
+func ValidateDuo(config *schema.Configuration, validator *schema.StructValidator) {
+ if config.DuoAPI.Disable {
+ return
+ }
+
+ if config.DuoAPI.Hostname == "" && config.DuoAPI.IntegrationKey == "" && config.DuoAPI.SecretKey == "" {
+ config.DuoAPI.Disable = true
+ }
+
+ if config.DuoAPI.Disable {
+ return
+ }
+
+ if config.DuoAPI.Hostname == "" {
+ validator.Push(fmt.Errorf(errFmtDuoMissingOption, "hostname"))
+ }
+
+ if config.DuoAPI.IntegrationKey == "" {
+ validator.Push(fmt.Errorf(errFmtDuoMissingOption, "integration_key"))
+ }
+
+ if config.DuoAPI.SecretKey == "" {
+ validator.Push(fmt.Errorf(errFmtDuoMissingOption, "secret_key"))
+ }
+}
diff --git a/internal/configuration/validator/duo_test.go b/internal/configuration/validator/duo_test.go
new file mode 100644
index 000000000..ef4856b56
--- /dev/null
+++ b/internal/configuration/validator/duo_test.go
@@ -0,0 +1,105 @@
+package validator
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/authelia/authelia/v4/internal/configuration/schema"
+)
+
+func TestValidateDuo(t *testing.T) {
+ testCases := []struct {
+ desc string
+ have *schema.Configuration
+ expected schema.DuoAPIConfiguration
+ errs []string
+ }{
+ {
+ desc: "ShouldDisableDuo",
+ have: &schema.Configuration{},
+ expected: schema.DuoAPIConfiguration{Disable: true},
+ },
+ {
+ desc: "ShouldNotDisableDuo",
+ have: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{
+ Hostname: "test",
+ IntegrationKey: "test",
+ SecretKey: "test",
+ }},
+ expected: schema.DuoAPIConfiguration{
+ Hostname: "test",
+ IntegrationKey: "test",
+ SecretKey: "test",
+ },
+ },
+ {
+ desc: "ShouldDetectMissingSecretKey",
+ have: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{
+ Hostname: "test",
+ IntegrationKey: "test",
+ }},
+ expected: schema.DuoAPIConfiguration{
+ Hostname: "test",
+ IntegrationKey: "test",
+ },
+ errs: []string{
+ "duo_api: option 'secret_key' is required when duo is enabled but it is missing",
+ },
+ },
+ {
+ desc: "ShouldDetectMissingIntegrationKey",
+ have: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{
+ Hostname: "test",
+ SecretKey: "test",
+ }},
+ expected: schema.DuoAPIConfiguration{
+ Hostname: "test",
+ SecretKey: "test",
+ },
+ errs: []string{
+ "duo_api: option 'integration_key' is required when duo is enabled but it is missing",
+ },
+ },
+ {
+ desc: "ShouldDetectMissingHostname",
+ have: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{
+ IntegrationKey: "test",
+ SecretKey: "test",
+ }},
+ expected: schema.DuoAPIConfiguration{
+ IntegrationKey: "test",
+ SecretKey: "test",
+ },
+ errs: []string{
+ "duo_api: option 'hostname' is required when duo is enabled but it is missing",
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ val := schema.NewStructValidator()
+
+ ValidateDuo(tc.have, val)
+
+ assert.Equal(t, tc.expected.Disable, tc.have.DuoAPI.Disable)
+ assert.Equal(t, tc.expected.Hostname, tc.have.DuoAPI.Hostname)
+ assert.Equal(t, tc.expected.IntegrationKey, tc.have.DuoAPI.IntegrationKey)
+ assert.Equal(t, tc.expected.SecretKey, tc.have.DuoAPI.SecretKey)
+ assert.Equal(t, tc.expected.EnableSelfEnrollment, tc.have.DuoAPI.EnableSelfEnrollment)
+
+ require.Len(t, val.Errors(), len(tc.errs))
+
+ if len(tc.errs) != 0 {
+ for i, err := range tc.errs {
+ t.Run(fmt.Sprintf("Err%d", i+1), func(t *testing.T) {
+ assert.EqualError(t, val.Errors()[i], err)
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/internal/configuration/validator/notifier.go b/internal/configuration/validator/notifier.go
index 56810a60d..b52bb646a 100644
--- a/internal/configuration/validator/notifier.go
+++ b/internal/configuration/validator/notifier.go
@@ -12,7 +12,7 @@ import (
// ValidateNotifier validates and update notifier configuration.
func ValidateNotifier(config *schema.NotifierConfiguration, validator *schema.StructValidator) {
- if config == nil || (config.SMTP == nil && config.FileSystem == nil) {
+ if config.SMTP == nil && config.FileSystem == nil {
validator.Push(fmt.Errorf(errFmtNotifierNotConfigured))
return
diff --git a/internal/handlers/handler_configuration_test.go b/internal/handlers/handler_configuration_test.go
index 480f80e2c..2812479d8 100644
--- a/internal/handlers/handler_configuration_test.go
+++ b/internal/handlers/handler_configuration_test.go
@@ -30,7 +30,9 @@ func (s *SecondFactorAvailableMethodsFixture) TearDownTest() {
func (s *SecondFactorAvailableMethodsFixture) TestShouldHaveAllConfiguredMethods() {
s.mock.Ctx.Configuration = schema.Configuration{
- DuoAPI: &schema.DuoAPIConfiguration{},
+ DuoAPI: schema.DuoAPIConfiguration{
+ Disable: false,
+ },
TOTP: schema.TOTPConfiguration{
Disable: false,
},
@@ -58,7 +60,9 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldHaveAllConfiguredMethods
func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveTOTPFromAvailableMethodsWhenDisabled() {
s.mock.Ctx.Configuration = schema.Configuration{
- DuoAPI: &schema.DuoAPIConfiguration{},
+ DuoAPI: schema.DuoAPIConfiguration{
+ Disable: false,
+ },
TOTP: schema.TOTPConfiguration{
Disable: true,
},
@@ -86,7 +90,9 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveTOTPFromAvailableM
func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveWebauthnFromAvailableMethodsWhenDisabled() {
s.mock.Ctx.Configuration = schema.Configuration{
- DuoAPI: &schema.DuoAPIConfiguration{},
+ DuoAPI: schema.DuoAPIConfiguration{
+ Disable: false,
+ },
TOTP: schema.TOTPConfiguration{
Disable: false,
},
@@ -114,7 +120,9 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveWebauthnFromAvaila
func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveDuoFromAvailableMethodsWhenNotConfigured() {
s.mock.Ctx.Configuration = schema.Configuration{
- DuoAPI: nil,
+ DuoAPI: schema.DuoAPIConfiguration{
+ Disable: true,
+ },
TOTP: schema.TOTPConfiguration{
Disable: false,
},
@@ -142,7 +150,9 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveDuoFromAvailableMe
func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveAllMethodsWhenNoTwoFactorACLRulesConfigured() {
s.mock.Ctx.Configuration = schema.Configuration{
- DuoAPI: &schema.DuoAPIConfiguration{},
+ DuoAPI: schema.DuoAPIConfiguration{
+ Disable: false,
+ },
TOTP: schema.TOTPConfiguration{
Disable: false,
},
@@ -170,7 +180,9 @@ func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveAllMethodsWhenNoTw
func (s *SecondFactorAvailableMethodsFixture) TestShouldRemoveAllMethodsWhenAllDisabledOrNotConfigured() {
s.mock.Ctx.Configuration = schema.Configuration{
- DuoAPI: nil,
+ DuoAPI: schema.DuoAPIConfiguration{
+ Disable: true,
+ },
TOTP: schema.TOTPConfiguration{
Disable: true,
},
diff --git a/internal/handlers/handler_reset_password_step2.go b/internal/handlers/handler_reset_password_step2.go
index 69a77caf1..ae3f814b6 100644
--- a/internal/handlers/handler_reset_password_step2.go
+++ b/internal/handlers/handler_reset_password_step2.go
@@ -80,7 +80,7 @@ func ResetPasswordPOST(ctx *middlewares.AutheliaCtx) {
bufHTML := new(bytes.Buffer)
disableHTML := false
- if ctx.Configuration.Notifier != nil && ctx.Configuration.Notifier.SMTP != nil {
+ if ctx.Configuration.Notifier.SMTP != nil {
disableHTML = ctx.Configuration.Notifier.SMTP.DisableHTMLEmails
}
diff --git a/internal/handlers/handler_user_info_test.go b/internal/handlers/handler_user_info_test.go
index e4d0fa659..47bc9a6b8 100644
--- a/internal/handlers/handler_user_info_test.go
+++ b/internal/handlers/handler_user_info_test.go
@@ -101,8 +101,6 @@ func TestUserInfoEndpoint_SetCorrectMethod(t *testing.T) {
mock := mocks.NewMockAutheliaCtx(t)
- mock.Ctx.Configuration.DuoAPI = &schema.DuoAPIConfiguration{}
-
// Set the initial user session.
userSession := mock.Ctx.GetSession()
userSession.Username = testUsername
@@ -172,9 +170,7 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
HasWebauthn: false,
HasDuo: false,
},
- config: &schema.Configuration{
- DuoAPI: &schema.DuoAPIConfiguration{},
- },
+ config: &schema.Configuration{},
loadErr: nil,
saveErr: nil,
},
@@ -192,9 +188,7 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
HasWebauthn: false,
HasDuo: true,
},
- config: &schema.Configuration{
- DuoAPI: &schema.DuoAPIConfiguration{},
- },
+ config: &schema.Configuration{},
loadErr: nil,
saveErr: nil,
},
@@ -212,6 +206,7 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
HasWebauthn: false,
HasDuo: true,
},
+ config: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{Disable: true}},
loadErr: nil,
saveErr: nil,
},
@@ -233,7 +228,6 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
TOTP: schema.TOTPConfiguration{
Disable: true,
},
- DuoAPI: &schema.DuoAPIConfiguration{},
},
loadErr: nil,
saveErr: nil,
@@ -252,104 +246,104 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
HasWebauthn: true,
HasDuo: true,
},
- config: &schema.Configuration{
- DuoAPI: &schema.DuoAPIConfiguration{},
- },
+ config: &schema.Configuration{},
loadErr: nil,
saveErr: errors.New("could not save"),
},
}
for _, resp := range expectedResponses {
- if resp.api == nil {
- resp.api = &resp.db
- }
+ t.Run(resp.description, func(t *testing.T) {
+ if resp.api == nil {
+ resp.api = &resp.db
+ }
- mock := mocks.NewMockAutheliaCtx(t)
+ mock := mocks.NewMockAutheliaCtx(t)
- if resp.config != nil {
- mock.Ctx.Configuration = *resp.config
- }
+ if resp.config != nil {
+ mock.Ctx.Configuration = *resp.config
+ }
- // Set the initial user session.
- userSession := mock.Ctx.GetSession()
- userSession.Username = testUsername
- userSession.AuthenticationLevel = 1
- err := mock.Ctx.SaveSession(userSession)
- require.NoError(t, err)
+ // Set the initial user session.
+ userSession := mock.Ctx.GetSession()
+ userSession.Username = testUsername
+ userSession.AuthenticationLevel = 1
+ err := mock.Ctx.SaveSession(userSession)
+ require.NoError(t, err)
- if resp.db.Method == "" {
- gomock.InOrder(
- mock.StorageMock.
- EXPECT().
- LoadPreferred2FAMethod(mock.Ctx, gomock.Eq("john")).
- Return("", sql.ErrNoRows),
- mock.StorageMock.
- EXPECT().
- SavePreferred2FAMethod(mock.Ctx, gomock.Eq("john"), gomock.Eq("")).
- Return(resp.saveErr),
- mock.StorageMock.
- EXPECT().
- LoadUserInfo(mock.Ctx, gomock.Eq("john")).
- Return(resp.db, nil),
- mock.StorageMock.EXPECT().
- SavePreferred2FAMethod(mock.Ctx, gomock.Eq("john"), gomock.Eq(resp.api.Method)).
- Return(resp.saveErr),
- )
- } else {
- gomock.InOrder(
- mock.StorageMock.
- EXPECT().
- LoadPreferred2FAMethod(mock.Ctx, gomock.Eq("john")).
- Return(resp.db.Method, nil),
- mock.StorageMock.
- EXPECT().
- LoadUserInfo(mock.Ctx, gomock.Eq("john")).
- Return(resp.db, nil),
- mock.StorageMock.EXPECT().
- SavePreferred2FAMethod(mock.Ctx, gomock.Eq("john"), gomock.Eq(resp.api.Method)).
- Return(resp.saveErr),
- )
- }
+ if resp.db.Method == "" {
+ gomock.InOrder(
+ mock.StorageMock.
+ EXPECT().
+ LoadPreferred2FAMethod(mock.Ctx, gomock.Eq("john")).
+ Return("", sql.ErrNoRows),
+ mock.StorageMock.
+ EXPECT().
+ SavePreferred2FAMethod(mock.Ctx, gomock.Eq("john"), gomock.Eq("")).
+ Return(resp.saveErr),
+ mock.StorageMock.
+ EXPECT().
+ LoadUserInfo(mock.Ctx, gomock.Eq("john")).
+ Return(resp.db, nil),
+ mock.StorageMock.EXPECT().
+ SavePreferred2FAMethod(mock.Ctx, gomock.Eq("john"), gomock.Eq(resp.api.Method)).
+ Return(resp.saveErr),
+ )
+ } else {
+ gomock.InOrder(
+ mock.StorageMock.
+ EXPECT().
+ LoadPreferred2FAMethod(mock.Ctx, gomock.Eq("john")).
+ Return(resp.db.Method, nil),
+ mock.StorageMock.
+ EXPECT().
+ LoadUserInfo(mock.Ctx, gomock.Eq("john")).
+ Return(resp.db, nil),
+ mock.StorageMock.EXPECT().
+ SavePreferred2FAMethod(mock.Ctx, gomock.Eq("john"), gomock.Eq(resp.api.Method)).
+ Return(resp.saveErr),
+ )
+ }
- UserInfoPOST(mock.Ctx)
+ UserInfoPOST(mock.Ctx)
- if resp.loadErr == nil && resp.saveErr == nil {
- t.Run(fmt.Sprintf("%s/%s", resp.description, "expected status code"), func(t *testing.T) {
- assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
- })
+ if resp.loadErr == nil && resp.saveErr == nil {
+ t.Run(fmt.Sprintf("%s/%s", resp.description, "expected status code"), func(t *testing.T) {
+ assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
+ })
- actualPreferences := model.UserInfo{}
+ actualPreferences := model.UserInfo{}
- mock.GetResponseData(t, &actualPreferences)
+ mock.GetResponseData(t, &actualPreferences)
- t.Run(fmt.Sprintf("%s/%s", resp.description, "expected method"), func(t *testing.T) {
- assert.Equal(t, resp.api.Method, actualPreferences.Method)
- })
+ t.Run("expected method", func(t *testing.T) {
+ assert.Equal(t, resp.api.Method, actualPreferences.Method)
+ })
- t.Run(fmt.Sprintf("%s/%s", resp.description, "registered webauthn"), func(t *testing.T) {
- assert.Equal(t, resp.api.HasWebauthn, actualPreferences.HasWebauthn)
- })
+ t.Run("registered webauthn", func(t *testing.T) {
+ assert.Equal(t, resp.api.HasWebauthn, actualPreferences.HasWebauthn)
+ })
- t.Run(fmt.Sprintf("%s/%s", resp.description, "registered totp"), func(t *testing.T) {
- assert.Equal(t, resp.api.HasTOTP, actualPreferences.HasTOTP)
- })
+ t.Run("registered totp", func(t *testing.T) {
+ assert.Equal(t, resp.api.HasTOTP, actualPreferences.HasTOTP)
+ })
- t.Run(fmt.Sprintf("%s/%s", resp.description, "registered duo"), func(t *testing.T) {
- assert.Equal(t, resp.api.HasDuo, actualPreferences.HasDuo)
- })
- } else {
- t.Run("expected status code", func(t *testing.T) {
- assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
- })
+ t.Run("registered duo", func(t *testing.T) {
+ assert.Equal(t, resp.api.HasDuo, actualPreferences.HasDuo)
+ })
+ } else {
+ t.Run("expected status code", func(t *testing.T) {
+ assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
+ })
- errResponse := mock.GetResponseError(t)
+ errResponse := mock.GetResponseError(t)
- assert.Equal(t, "KO", errResponse.Status)
- assert.Equal(t, "Operation failed.", errResponse.Message)
- }
+ assert.Equal(t, "KO", errResponse.Status)
+ assert.Equal(t, "Operation failed.", errResponse.Message)
+ }
- mock.Close()
+ mock.Close()
+ })
}
}
@@ -420,7 +414,7 @@ func (s *SaveSuite) TestShouldReturnError500WhenBadMethodProvided() {
MethodPreferencePOST(s.mock.Ctx)
s.mock.Assert200KO(s.T(), "Operation failed.")
- assert.Equal(s.T(), "unknown or unavailable method 'abc', it should be one of totp, webauthn", s.mock.Hook.LastEntry().Message)
+ assert.Equal(s.T(), "unknown or unavailable method 'abc', it should be one of totp, webauthn, mobile_push", s.mock.Hook.LastEntry().Message)
assert.Equal(s.T(), logrus.ErrorLevel, s.mock.Hook.LastEntry().Level)
}
diff --git a/internal/middlewares/authelia_context.go b/internal/middlewares/authelia_context.go
index 4b209176b..57805401e 100644
--- a/internal/middlewares/authelia_context.go
+++ b/internal/middlewares/authelia_context.go
@@ -67,7 +67,7 @@ func (ctx *AutheliaCtx) AvailableSecondFactorMethods() (methods []string) {
methods = append(methods, model.SecondFactorMethodWebauthn)
}
- if ctx.Configuration.DuoAPI != nil {
+ if !ctx.Configuration.DuoAPI.Disable {
methods = append(methods, model.SecondFactorMethodDuo)
}
diff --git a/internal/middlewares/authelia_context_test.go b/internal/middlewares/authelia_context_test.go
index 0dbb29512..e120b8c29 100644
--- a/internal/middlewares/authelia_context_test.go
+++ b/internal/middlewares/authelia_context_test.go
@@ -121,9 +121,11 @@ func TestShouldReturnCorrectSecondFactorMethods(t *testing.T) {
mock := mocks.NewMockAutheliaCtx(t)
defer mock.Close()
+ mock.Ctx.Configuration.DuoAPI.Disable = true
+
assert.Equal(t, []string{model.SecondFactorMethodTOTP, model.SecondFactorMethodWebauthn}, mock.Ctx.AvailableSecondFactorMethods())
- mock.Ctx.Configuration.DuoAPI = &schema.DuoAPIConfiguration{}
+ mock.Ctx.Configuration.DuoAPI.Disable = false
assert.Equal(t, []string{model.SecondFactorMethodTOTP, model.SecondFactorMethodWebauthn, model.SecondFactorMethodDuo}, mock.Ctx.AvailableSecondFactorMethods())
@@ -135,7 +137,7 @@ func TestShouldReturnCorrectSecondFactorMethods(t *testing.T) {
assert.Equal(t, []string{model.SecondFactorMethodDuo}, mock.Ctx.AvailableSecondFactorMethods())
- mock.Ctx.Configuration.DuoAPI = nil
+ mock.Ctx.Configuration.DuoAPI.Disable = true
assert.Equal(t, []string{}, mock.Ctx.AvailableSecondFactorMethods())
}
diff --git a/internal/middlewares/identity_verification.go b/internal/middlewares/identity_verification.go
index db6c7076d..0658978b7 100644
--- a/internal/middlewares/identity_verification.go
+++ b/internal/middlewares/identity_verification.go
@@ -73,7 +73,7 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim
bufHTML := new(bytes.Buffer)
disableHTML := false
- if ctx.Configuration.Notifier != nil && ctx.Configuration.Notifier.SMTP != nil {
+ if ctx.Configuration.Notifier.SMTP != nil {
disableHTML = ctx.Configuration.Notifier.SMTP.DisableHTMLEmails
}
diff --git a/internal/server/handlers.go b/internal/server/handlers.go
index aea535e91..b7f726823 100644
--- a/internal/server/handlers.go
+++ b/internal/server/handlers.go
@@ -87,7 +87,7 @@ func getHandler(config schema.Configuration, providers middlewares.Providers) fa
resetPasswordCustomURL := config.AuthenticationBackend.PasswordReset.CustomURL.String()
duoSelfEnrollment := f
- if config.DuoAPI != nil {
+ if !config.DuoAPI.Disable {
duoSelfEnrollment = strconv.FormatBool(config.DuoAPI.EnableSelfEnrollment)
}
@@ -184,7 +184,7 @@ func getHandler(config schema.Configuration, providers middlewares.Providers) fa
}
// Configure DUO api endpoint only if configuration exists.
- if config.DuoAPI != nil {
+ if !config.DuoAPI.Disable {
var duoAPI duo.API
if os.Getenv("ENVIRONMENT") == dev {
duoAPI = duo.NewDuoAPI(duoapi.NewDuoApi(