Skip to content

Commit

Permalink
refactor: use pydantic to validate otp sms and email configurations
Browse files Browse the repository at this point in the history
  • Loading branch information
azmeuk committed Jan 30, 2025
1 parent 4031144 commit c8f98b4
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 26 deletions.
13 changes: 0 additions & 13 deletions canaille/app/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
16 changes: 15 additions & 1 deletion canaille/core/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,28 @@ 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."
)

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.
Expand Down
14 changes: 2 additions & 12 deletions tests/app/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,17 +241,13 @@ 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,
match=r"Path does not point to a directory",
):
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(
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand Down

0 comments on commit c8f98b4

Please sign in to comment.