From c8f98b4a22551ca048436f09b609907227c96b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Thu, 30 Jan 2025 10:57:49 +0100 Subject: [PATCH] refactor: use pydantic to validate otp sms and email configurations --- canaille/app/configuration.py | 13 ------------- canaille/core/configuration.py | 16 +++++++++++++++- tests/app/test_configuration.py | 14 ++------------ 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/canaille/app/configuration.py b/canaille/app/configuration.py index 1c35248c..815abd42 100644 --- a/canaille/app/configuration.py +++ b/canaille/app/configuration.py @@ -209,7 +209,6 @@ def setup_config(app, config=None, test_config=True, env_file=None, env_prefix=" def validate(config, validate_remote=False): validate_keypair(config.get("CANAILLE_OIDC")) - validate_otp_config(config["CANAILLE"]) if not validate_remote: return @@ -287,18 +286,6 @@ def validate_smpp_configuration(config): raise ConfigurationException(exc) from exc -def validate_otp_config(config): - if config["EMAIL_OTP"] and not config["SMTP"]: - raise ConfigurationException( - "Cannot activate email one-time password authentication without SMTP" - ) - - if config["SMS_OTP"] and not config["SMPP"]: - raise ConfigurationException( - "Cannot activate sms one-time password authentication without SMPP" - ) - - def sanitize_rst_text(text: str) -> str: """Remove inline RST syntax.""" # Replace :foo:`~bar.Baz` with Baz diff --git a/canaille/core/configuration.py b/canaille/core/configuration.py index df98c799..b807db99 100644 --- a/canaille/core/configuration.py +++ b/canaille/core/configuration.py @@ -311,7 +311,7 @@ class CoreSettings(BaseModel): @field_validator("OTP_METHOD", "EMAIL_OTP") @classmethod - def validate_otp_method(cls, value): + def validate_otp_dependency(cls, value): if not importlib.util.find_spec("otpauth"): # pragma: no cover raise ValidationError( "You are trying to use OTP but the 'otp' extra is not installed." @@ -319,6 +319,20 @@ def validate_otp_method(cls, value): return value + @model_validator(mode="after") + def validate_otp_configuration(self) -> Self: + if self.EMAIL_OTP and not self.SMTP: + raise ValueError( + "Cannot activate email one-time password authentication without SMTP" + ) + + if self.SMS_OTP and not self.SMPP: + raise ValueError( + "Cannot activate sms one-time password authentication without SMPP" + ) + + return self + INVITATION_EXPIRATION: int = 172800 """The validity duration of registration invitations, in seconds. diff --git a/tests/app/test_configuration.py b/tests/app/test_configuration.py index 85180fb0..5ecd68a0 100644 --- a/tests/app/test_configuration.py +++ b/tests/app/test_configuration.py @@ -241,8 +241,6 @@ def test_invalid_theme(configuration, backend): ): configuration["CANAILLE"]["THEME"] = "invalid" config_obj = settings_factory(configuration) - config_dict = config_obj.model_dump() - validate(config_dict, validate_remote=False) with pytest.raises( ValidationError, @@ -250,8 +248,6 @@ def test_invalid_theme(configuration, backend): ): configuration["CANAILLE"]["THEME"] = "/path/to/invalid" config_obj = settings_factory(configuration) - config_dict = config_obj.model_dump() - validate(config_dict, validate_remote=False) def test_enable_password_compromission_check_with_and_without_admin_email( @@ -288,8 +284,6 @@ def test_invalid_otp_option(configuration, backend): ): configuration["CANAILLE"]["OTP_METHOD"] = "invalid" config_obj = settings_factory(configuration) - config_dict = config_obj.model_dump() - validate(config_dict, validate_remote=False) def test_email_otp_without_smtp(configuration, backend): @@ -299,14 +293,12 @@ def test_email_otp_without_smtp(configuration, backend): validate(config_dict, validate_remote=False) with pytest.raises( - ConfigurationException, + ValidationError, match=r"Cannot activate email one-time password authentication without SMTP", ): configuration["CANAILLE"]["SMTP"] = None configuration["CANAILLE"]["EMAIL_OTP"] = True config_obj = settings_factory(configuration) - config_dict = config_obj.model_dump() - validate(config_dict, validate_remote=False) def test_sms_otp_without_smpp(configuration, backend): @@ -316,14 +308,12 @@ def test_sms_otp_without_smpp(configuration, backend): validate(config_dict, validate_remote=False) with pytest.raises( - ConfigurationException, + ValidationError, match=r"Cannot activate sms one-time password authentication without SMPP", ): configuration["CANAILLE"]["SMPP"] = None configuration["CANAILLE"]["SMS_OTP"] = True config_obj = settings_factory(configuration) - config_dict = config_obj.model_dump() - validate(config_dict, validate_remote=False) def test_smpp_connection_remote_smpp_unreachable(testclient, backend, configuration):