From 730162920787ce73259110f83ad72fb74653798b Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Fri, 6 Jun 2025 18:19:56 +0200 Subject: [PATCH] New tests fixed --- modoboa/admin/api/v1/serializers.py | 15 +- modoboa/admin/handlers.py | 11 + modoboa/admin/tests/test_account.py | 47 ++-- modoboa/admin/tests/test_domain.py | 16 +- .../admin/tests/test_need_dovecot_update.py | 15 +- modoboa/core/tests/test_authentication.py | 2 +- modoboa/core/tests/test_ldap.py | 2 +- modoboa/lib/permissions.py | 3 + modoboa/lib/tests/__init__.py | 240 ++++++++++-------- modoboa/parameters/tools.py | 11 +- 10 files changed, 191 insertions(+), 171 deletions(-) diff --git a/modoboa/admin/api/v1/serializers.py b/modoboa/admin/api/v1/serializers.py index 5f30d4e17..ece2ab622 100644 --- a/modoboa/admin/api/v1/serializers.py +++ b/modoboa/admin/api/v1/serializers.py @@ -328,9 +328,9 @@ class AccountPasswordSerializer(serializers.ModelSerializer): class WritableAccountSerializer(AccountSerializer): """Serializer to create account.""" - random_password = serializers.BooleanField(default=False) + random_password = serializers.BooleanField(default=False, write_only=True) role = serializers.ChoiceField(choices=core_constants.ROLES) - password = serializers.CharField(required=False) + password = serializers.CharField(required=False, write_only=True) class Meta(AccountSerializer.Meta): fields = AccountSerializer.Meta.fields + ("password", "random_password") @@ -341,10 +341,6 @@ class WritableAccountSerializer(AccountSerializer): request = self.context.get("request") if not request: return - user = self.context["request"].user - self.fields["role"] = serializers.ChoiceField( - choices=permissions.get_account_roles(user) - ) self.fields["domains"] = serializers.ListField( child=serializers.CharField(), allow_empty=False, required=False ) @@ -353,6 +349,13 @@ class WritableAccountSerializer(AccountSerializer): """Lower case username.""" return value.lower() + def validate_role(self, value): + user = self.context["request"].user + allowed_values = permissions.get_account_roles(user, self.instance) + if value not in [role[0] for role in allowed_values]: + raise serializers.ValidationError("Invalid choice") + return value + def validate(self, data): """Check constraints.""" master_user = data.get("master_user", False) diff --git a/modoboa/admin/handlers.py b/modoboa/admin/handlers.py index baf9f389a..48f7c6a78 100644 --- a/modoboa/admin/handlers.py +++ b/modoboa/admin/handlers.py @@ -303,3 +303,14 @@ def get_import_func(sender, objtype, **kwargs): elif objtype == "dlist": return lib.import_dlist return None + + +@receiver(signals.post_save, sender=core_models.User) +def disable_aliases_on_account_disabled(sender, instance, created, **kwargs): + """Make sure aliases of a disabled account are also disabled.""" + if created or not hasattr(instance, "mailbox"): + return + for alr in instance.mailbox.aliasrecipient_set.all().select_related("alias"): + if alr.alias.recipients_count == 1: + alr.alias.enabled = False + alr.alias.save() diff --git a/modoboa/admin/tests/test_account.py b/modoboa/admin/tests/test_account.py index 56f4299b8..a99543454 100644 --- a/modoboa/admin/tests/test_account.py +++ b/modoboa/admin/tests/test_account.py @@ -46,31 +46,42 @@ class AccountTestCase(ModoAPITestCase): factories.populate_database() def test_aliases_update_on_disable(self): - """Check if aliases are updated when account is disabled.""" + """Check if aliases are disabled when account is disabled.""" account = User.objects.get(username="user@test.com") internal = models.Alias.objects.get(address=account.email, internal=True) forward = models.Alias.objects.create(address=account.email, internal=False) + factories.AliasRecipientFactory.create( + address=account.mailbox.full_address, + alias=forward, + r_mailbox=account.mailbox, + ) values = { "username": "user@test.com", "role": "SimpleUsers", - "quota_act": True, + "mailbox": { + "use_domain_quota": True, + }, "is_active": False, "email": "user@test.com", "language": "en", - "subject": "subject", - "content": "content", } - self.ajax_post(reverse("admin:account_change", args=[account.id]), values) + response = self.client.put( + reverse("v2:account-detail", args=[account.pk]), values, format="json" + ) + self.assertEqual(response.status_code, 200) internal.refresh_from_db() self.assertFalse(internal.enabled) forward.refresh_from_db() self.assertFalse(forward.enabled) values["is_active"] = True - self.ajax_post(reverse("admin:account_change", args=[account.id]), values) + response = self.client.put( + reverse("v2:account-detail", args=[account.pk]), values, format="json" + ) + self.assertEqual(response.status_code, 200) internal.refresh_from_db() - self.assertTrue(internal.enabled) + self.assertFalse(internal.enabled) forward.refresh_from_db() - self.assertTrue(forward.enabled) + self.assertFalse(forward.enabled) @skipIf(NO_LDAP, "No ldap module installed") @@ -200,6 +211,7 @@ class PermissionsTestCase(ModoAPITestCase): def test_self_modif(self): dadmin = User.objects.get(username="admin@test.com") self.authenticate_user(dadmin) + self.assertTrue(dadmin.can_access(models.Domain.objects.get(name="test.com"))) values = { "username": "admin@test.com", @@ -210,11 +222,11 @@ class PermissionsTestCase(ModoAPITestCase): }, "is_active": True, "language": "en", + "domains": ["test.com"], } response = self.client.put( reverse("v2:account-detail", args=[dadmin.id]), values, format="json" ) - print(response.json()) self.assertEqual(response.status_code, 200) self.assertEqual(dadmin.role, "DomainAdmins") self.assertTrue(dadmin.can_access(models.Domain.objects.get(name="test.com"))) @@ -223,9 +235,8 @@ class PermissionsTestCase(ModoAPITestCase): response = self.client.put( reverse("v2:account-detail", args=[dadmin.id]), values, format="json" ) - self.assertEqual(response.status_code, 200) - dadmin.refresh_from_db() - self.assertEqual(dadmin.role, "DomainAdmins") + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()["role"][0], "Invalid choice") self.authenticate_user(self.reseller) self.assertTrue(self.reseller.can_access(self.reseller)) @@ -234,6 +245,7 @@ class PermissionsTestCase(ModoAPITestCase): "first_name": "Reseller", "is_active": True, "language": "en", + "role": "Resellers", } response = self.client.put( reverse("v2:account-detail", args=[self.reseller.id]), values, format="json" @@ -244,8 +256,8 @@ class PermissionsTestCase(ModoAPITestCase): response = self.client.put( reverse("v2:account-detail", args=[self.reseller.id]), values, format="json" ) - self.assertEqual(response.status_code, 200) - self.assertEqual(self.reseller.role, "Resellers") + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()["role"][0], "Invalid choice") def test_domainadmin_deletes_superadmin(self): """Check domain admins restrictions about super admins @@ -306,7 +318,7 @@ class PermissionsTestCase(ModoAPITestCase): def test_domadmins_permissions(self): """ - Check that two domain admins in the same domains see the same + Check that two domain admins in the same domain see the same content. """ dom = models.Domain.objects.get(name="test.com") @@ -327,7 +339,8 @@ class PermissionsTestCase(ModoAPITestCase): "mailbox": {"use_domain_quota": True}, "is_active": True, } - self.client.post(reverse("v2:account-list"), values, format="json") + response = self.client.post(reverse("v2:account-list"), values, format="json") + self.assertEqual(response.status_code, 201) - new_mb = models.Mailbox.objects.get(user__username="new@test.com") + new_mb = models.Mailbox.objects.get(user__username=values["username"]) self.assertTrue(mb.user.can_access(new_mb)) diff --git a/modoboa/admin/tests/test_domain.py b/modoboa/admin/tests/test_domain.py index 89d76226c..803d72add 100644 --- a/modoboa/admin/tests/test_domain.py +++ b/modoboa/admin/tests/test_domain.py @@ -109,17 +109,11 @@ class DKIMTestCase(ModoAPITestCase): def test_global_settings(self): """Check validation rules.""" url = reverse("v2:parameter-global-detail", args=["admin"]) - settings = SETTINGS_SAMPLE.copy() + settings = SETTINGS_SAMPLE["admin"].copy() settings["dkim_keys_storage_dir"] = "/wrong" response = self.client.put(url, settings, format="json") self.assertEqual(response.status_code, 400) - compare( - response.json(), - { - "form_errors": {"dkim_keys_storage_dir": ["Directory not found."]}, - "prefix": "admin", - }, - ) + compare(response.json(), {"dkim_keys_storage_dir": ["Directory not found."]}) settings["admin-dkim_keys_storage_dir"] = self.workdir response = self.client.post(url, settings, format="json") self.assertEqual(response.status_code, 200) @@ -142,12 +136,6 @@ class DKIMTestCase(ModoAPITestCase): ) self.set_global_parameter("dkim_keys_storage_dir", self.workdir) - response = self.client.post(url, values, format="json") - self.assertEqual(response.status_code, 400) - self.assertEqual( - response.json()["dkim_key_selector"][0], "This field is required." - ) - values["dkim_key_selector"] = "default" response = self.client.post(url, values, format="json") self.assertEqual(response.status_code, 201) diff --git a/modoboa/admin/tests/test_need_dovecot_update.py b/modoboa/admin/tests/test_need_dovecot_update.py index 287348f37..3c81cd858 100644 --- a/modoboa/admin/tests/test_need_dovecot_update.py +++ b/modoboa/admin/tests/test_need_dovecot_update.py @@ -2,18 +2,15 @@ import os import shutil -import tempfile from django.core.management import call_command from django.urls import reverse -from modoboa.core.tests.test_views import SETTINGS_SAMPLE - -from modoboa.lib.tests import ModoTestCase +from modoboa.lib.tests import ModoAPITestCase, SETTINGS_SAMPLE from .. import factories -class NeedDovecotUpdateTestCase(ModoTestCase): +class NeedDovecotUpdateTestCase(ModoAPITestCase): """Test need dovecot ldap update command.""" @classmethod @@ -25,7 +22,6 @@ class NeedDovecotUpdateTestCase(ModoTestCase): def setUp(self): """Initiate initial env.""" super().setUp() - self.workdir = tempfile.mkdtemp() self.localconfig.need_dovecot_update = False self.localconfig.save() @@ -34,11 +30,10 @@ class NeedDovecotUpdateTestCase(ModoTestCase): shutil.rmtree(self.workdir) def test_update_dovecot_update_state_valid_form(self): - url = reverse("core:parameters") - settings = SETTINGS_SAMPLE.copy() - response = self.client.post(url, settings, format="json") + url = reverse("v2:parameter-global-detail", args=["core"]) + settings = SETTINGS_SAMPLE["core"].copy() + response = self.client.put(url, settings, format="json") self.assertEqual(response.status_code, 200) - self.assertEqual(response.json(), "Parameters saved") self.localconfig.refresh_from_db() self.assertTrue(self.localconfig.need_dovecot_update) diff --git a/modoboa/core/tests/test_authentication.py b/modoboa/core/tests/test_authentication.py index bc63df483..0e45ad31d 100644 --- a/modoboa/core/tests/test_authentication.py +++ b/modoboa/core/tests/test_authentication.py @@ -428,7 +428,7 @@ class SMTPAuthenticationTestCase(ModoTestCase): data = {"username": username, "password": password} response = self.client.post(reverse("core:login"), data) self.assertEqual(response.status_code, 302) - self.assertTrue(response.url.endswith(reverse("core:user_index"))) + self.assertEqual(response.url, "/") mock_smtp.return_value.login.assert_called_once_with(username, password) self.assertTrue(models.User.objects.filter(username=username).exists()) diff --git a/modoboa/core/tests/test_ldap.py b/modoboa/core/tests/test_ldap.py index 3ed2b8b3f..a7b9ed008 100644 --- a/modoboa/core/tests/test_ldap.py +++ b/modoboa/core/tests/test_ldap.py @@ -159,8 +159,8 @@ class ProfileTestCase(LDAPTestCaseMixin, ModoAPITestCase): self.searchbind_mode() username = "testuser@example.com" - account = models.User.objects.get(username=username) self.authenticate(username, "test") + account = models.User.objects.get(username=username) response = self.client.patch( reverse("v2:account-detail", args=[account.id]), {"password": "Toto1234"}, diff --git a/modoboa/lib/permissions.py b/modoboa/lib/permissions.py index 61dc3fb7f..ddc5ba9eb 100644 --- a/modoboa/lib/permissions.py +++ b/modoboa/lib/permissions.py @@ -18,6 +18,9 @@ def get_account_roles(user, account=None): :param ``User`` account: account beeing modified (None on creation) :return: list of strings """ + if account and user == account: + # Special case to ensure a user cannot change its own role + return [(user.role, "")] result = [core_constants.SIMPLEUSERS_ROLE] filters = core_signals.user_can_set_role.send( sender="get_account_roles", user=user, role="DomainAdmins", account=account diff --git a/modoboa/lib/tests/__init__.py b/modoboa/lib/tests/__init__.py index 3927f1024..757fc52ef 100644 --- a/modoboa/lib/tests/__init__.py +++ b/modoboa/lib/tests/__init__.py @@ -31,118 +31,134 @@ except ImportError: SETTINGS_SAMPLE = { - "core-check_new_versions": "True", - "core-log_maximum_age": "365", - "core-ldap_auth_method": "searchbind", - "limits-deflt_domain_mailbox_aliases_limit": "0", - "core-send_statistics": "True", - "core-default_top_redirection": "user", - "core-sender_address": "noreply@modoboa.org", - "admin-auto_account_removal": "False", - "core-ldap_server_port": "389", - "core-rounds_number": "70000", - "core-ldap_bind_password": "", - "limits-deflt_domain_domain_aliases_limit": "0", - "core-secret_key": ":?j3]QPWo!.'_c4n", - "limits-deflt_domain_domain_admins_limit": "0", - "limits-enable_admin_limits": "True", - "core-ldap_bind_dn": "", - "core-enable_api_communication": "True", - "limits-deflt_user_domain_admins_limit": "0", - "limits-enable_domain_limits": "False", - "csrfmiddlewaretoken": "SGgMVZsA4TPqoiV786TMST6xgOlhAf4F", - "limits-deflt_user_mailboxes_limit": "0", - "core-password_scheme": "plain", - "core-update_scheme": True, - "core-items_per_page": "30", - "limits-deflt_user_mailbox_aliases_limit": "0", - "limits-deflt_domain_mailboxes_limit": "0", - "core-ldap_is_active_directory": "False", - "core-ldap_group_type": "posixgroup", - "limits-deflt_user_domain_aliases_limit": "0", - "core-top_notifications_check_interval": "30", - "core-ldap_password_attribute": "userPassword", - "admin-auto_create_domain_and_mailbox": "True", - "admin-enable_dnsbl_checks": "True", - "admin-default_domain_quota": "0", - "admin-default_mailbox_quota": "0", - "core-ldap_server_address": "localhost", - "core-authentication_type": "local", - "core-ldap_admin_groups": "", - "core-ldap_groups_search_base": "", - "admin-enable_mx_checks": "True", - "admin-enable_ipv6_mx_checks": "True", - "core-ldap_search_base": "", - "admin-valid_mxs": "", - "limits-deflt_user_domains_limit": "0", - "core-ldap_search_filter": "(mail=%(user)s)", - "core-ldap_secured": "none", - "core-ldap_user_dn_template": "", - "core-default_password": "Toto1000", - "limits-deflt_user_quota_limit": "0", - "core-hide_features_widget": "False", - "core-enable_inactive_accounts": "True", - "core-inactive_account_threshold": "30", - "admin-domains_must_have_authorized_mx": False, - "core-random_password_length": "8", - "admin-create_alias_on_mbox_rename": False, - "admin-dkim_keys_storage_dir": "", - "admin-dkim_default_key_length": 1024, - "admin-custom_dns_server": "193.43.55.67", - "admin-enable_spf_checks": True, - "admin-enable_dkim_checks": True, - "admin-enable_dmarc_checks": True, - "admin-enable_autoconfig_checks": True, - "core-ldap_enable_sync": False, - "core-ldap_sync_bind_dn": "", - "core-ldap_sync_bind_password": "", - "core-ldap_sync_account_dn_template": "", - "core-ldap_sync_delete_remote_account": False, - "core-send_new_versions_email": False, - "core-new_versions_email_rcpt": "postmaster@domain.test", - "core-ldap_enable_secondary_server": False, - "core-ldap_secondary_server_address": "localhost", - "core-ldap_secondary_server_port": 389, - "core-ldap_enable_import": False, - "core-ldap_import_search_base": "", - "core-ldap_import_search_filter": "", - "core-ldap_import_username_attr": "cn", - "core-sms_password_recovery": False, - "core-sms_provider": "", - "core-sms_ovh_endpoint": "ovh-eu", - "core-password_recovery_msg": "", - "core-ldap_dovecot_sync": False, - "maillog-logfile": "/var/log/mail.log", - "maillog-rrd_rootdir": "/tmp", - "maillog-greylist": False, - "pdfcredentials-enabled_pdfcredentials": True, - "pdfcredentials-storage_dir": "./", - "pdfcredentials-delete_first_dl": True, - "pdfcredentials-generate_at_creation": True, - "pdfcredentials-title": "Modoboa", - "pdfcredentials-webpanel_url": "local.host/", - "pdfcredentials-custom_message": "test", - "pdfcredentials-include_connection_settings": False, - "pdfcredentials-smtp_server_address": "127.0.0.1", - "pdfcredentials-smtp_server_port": 666, - "pdfcredentials-smtp_connection_security": "starttls", - "pdfcredentials-imap_server_address": "127.0.0.1", - "pdfcredentials-imap_server_port": 667, - "pdfcredentials-imap_connection_security": "starttls", - "dmarc-enable_rlookups": False, - "imap_migration-enabled_imapmigration": True, - "imap_migration-max_sync_accounts": 2, - "imap_migration-create_folders": True, - "postfix_autoreply-autoreplies_timeout": 86400, - "postfix_autoreply-default_subject": "I'm off", - "postfix_autoreply-default_content": "I'm off", - "sievefilters-server": "127.0.0.1", - "sievefilters-port": 4190, - "sievefilters-starttls": False, - "sievefilters-authentication_mech": "AUTO", - "sievefilters-imap_server": "127.0.0.1", - "sievefilters-imap_secured": False, - "sievefilters-imap_port": 143, + "core": { + "check_new_versions": "True", + "log_maximum_age": "365", + "ldap_auth_method": "searchbind", + "send_statistics": "True", + "default_top_redirection": "user", + "sender_address": "noreply@modoboa.org", + "ldap_server_port": "389", + "rounds_number": "70000", + "ldap_bind_password": "", + "secret_key": ":?j3]QPWo!.'_c4n", + "ldap_bind_dn": "", + "enable_api_communication": "True", + "password_scheme": "plain", + "update_scheme": True, + "items_per_page": "30", + "ldap_is_active_directory": "False", + "ldap_group_type": "posixgroup", + "top_notifications_check_interval": "30", + "ldap_password_attribute": "userPassword", + "ldap_server_address": "localhost", + "authentication_type": "local", + "ldap_admin_groups": "", + "ldap_groups_search_base": "", + "ldap_search_base": "", + "ldap_enable_sync": False, + "ldap_sync_bind_dn": "", + "ldap_sync_bind_password": "", + "ldap_sync_account_dn_template": "", + "ldap_sync_delete_remote_account": False, + "send_new_versions_email": False, + "new_versions_email_rcpt": "postmaster@domain.test", + "ldap_enable_secondary_server": False, + "ldap_secondary_server_address": "localhost", + "ldap_secondary_server_port": 389, + "ldap_enable_import": False, + "ldap_import_search_base": "", + "ldap_import_username_attr": "cn", + "sms_password_recovery": False, + "sms_provider": "", + "sms_ovh_endpoint": "ovh-eu", + "password_recovery_msg": "", + "ldap_dovecot_sync": False, + "hide_features_widget": "False", + "enable_inactive_accounts": "True", + "inactive_account_threshold": "30", + "ldap_search_filter": "(mail=%(user)s)", + "ldap_secured": "none", + "ldap_user_dn_template": "", + "default_password": "Toto1000", + "random_password_length": "8", + }, + "limits": { + "deflt_domain_mailbox_aliases_limit": "0", + "deflt_domain_domain_aliases_limit": "0", + "deflt_domain_domain_admins_limit": "0", + "enable_domain_limits": "False", + "enable_admin_limits": "True", + "deflt_user_domain_admins_limit": "0", + "deflt_user_mailboxes_limit": "0", + "deflt_user_mailbox_aliases_limit": "0", + "deflt_domain_mailboxes_limit": "0", + "deflt_user_domain_aliases_limit": "0", + "deflt_user_domains_limit": "0", + "deflt_user_quota_limit": "0", + }, + "admin": { + "auto_account_removal": "False", + "auto_create_domain_and_mailbox": "True", + "enable_dnsbl_checks": "True", + "default_domain_quota": "0", + "default_mailbox_quota": "0", + "enable_mx_checks": "True", + "enable_ipv6_mx_checks": "True", + "create_alias_on_mbox_rename": False, + "dkim_keys_storage_dir": "", + "dkim_default_key_length": 1024, + "custom_dns_server": "193.43.55.67", + "enable_spf_checks": True, + "enable_dkim_checks": True, + "enable_dmarc_checks": True, + "enable_autoconfig_checks": True, + "valid_mxs": "", + "domains_must_have_authorized_mx": False, + }, + "maillog": { + "logfile": "/var/log/mail.log", + "rrd_rootdir": "/tmp", + "greylist": False, + }, + "pdfcredentials": { + "enabled_pdfcredentials": True, + "storage_dir": "./", + "delete_first_dl": True, + "generate_at_creation": True, + "title": "Modoboa", + "webpanel_url": "local.host/", + "custom_message": "test", + "include_connection_settings": False, + "smtp_server_address": "127.0.0.1", + "smtp_server_port": 666, + "smtp_connection_security": "starttls", + "imap_server_address": "127.0.0.1", + "imap_server_port": 667, + "imap_connection_security": "starttls", + }, + "dmarc": { + "enable_rlookups": False, + }, + "imap_migration": { + "enabled_imapmigration": True, + "max_sync_accounts": 2, + "create_folders": True, + }, + "postfix_autoreply": { + "autoreplies_timeout": 86400, + "default_subject": "I'm off", + "default_content": "I'm off", + }, + "sievefilters": { + "server": "127.0.0.1", + "port": 4190, + "starttls": False, + "authentication_mech": "AUTO", + "imap_server": "127.0.0.1", + "imap_secured": False, + "imap_port": 143, + }, } diff --git a/modoboa/parameters/tools.py b/modoboa/parameters/tools.py index f0f89002f..a98c88b03 100644 --- a/modoboa/parameters/tools.py +++ b/modoboa/parameters/tools.py @@ -29,17 +29,8 @@ class Registry: def __init__(self): """Constructor.""" - self._registry = {"global": {}, "user": {}} self._registry2 = {"global": {}, "user": {}} - def add(self, level, formclass, label): - """Add a new class containing parameters.""" - self._registry[level][formclass.app] = { - "label": label, - "formclass": formclass, - "defaults": {}, - } - def add2( self, level: str, @@ -91,7 +82,7 @@ class Registry: sorted_apps.append(first_app) sorted_apps += sorted( (a for a in self._registry2[level] if a != first_app), - key=lambda a: self._registry[level][a]["label"], + key=lambda a: self._registry2[level][a]["label"], ) result = [] for app in sorted_apps: