From f1863e61a63d4957236713e9b519c5b8b426a233 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Wed, 27 Apr 2016 11:32:48 +0200 Subject: [PATCH] Domain admins limit added. see #799 --- modoboa/admin/models/domain.py | 17 +++++++--- modoboa/limits/app_settings.py | 25 ++++++++------- modoboa/limits/constants.py | 4 +++ modoboa/limits/controls.py | 7 ++-- modoboa/limits/general_callbacks.py | 18 ++++++++--- modoboa/limits/tests/test_domain_limits.py | 37 +++++++++++++++++++--- modoboa/limits/tests/test_import.py | 18 +++++++---- 7 files changed, 94 insertions(+), 32 deletions(-) diff --git a/modoboa/admin/models/domain.py b/modoboa/admin/models/domain.py index cd4d425d8..bf5197adf 100644 --- a/modoboa/admin/models/domain.py +++ b/modoboa/admin/models/domain.py @@ -8,11 +8,13 @@ from django.utils.translation import ugettext as _, ugettext_lazy import reversion -from .base import AdminObject from modoboa.core.models import User, ObjectAccess +from modoboa.core import signals as core_signals from modoboa.lib import events, parameters from modoboa.lib.exceptions import BadRequest, Conflict +from .base import AdminObject + class DomainManager(Manager): @@ -85,23 +87,28 @@ class Domain(AdminObject): @property def admins(self): - """Return the domain administrators of this domain + """Return the domain administrators of this domain. :return: a list of User objects """ - return [oa.user for oa in self.owners.filter(user__is_superuser=False)] + return User.objects.filter( + is_superuser=False, + objectaccess__content_type__model="domain", + objectaccess__object_id=self.pk) @property def aliases(self): return self.domainalias_set def add_admin(self, account): - """Add a new administrator for this domain + """Add a new administrator to this domain. - :param User account: the administrotor to add + :param User account: the administrator """ from modoboa.lib.permissions import grant_access_to_object + core_signals.can_create_object.send( + sender=self.__class__, context=self, object_type="domain_admins") grant_access_to_object(account, self) for mb in self.mailbox_set.all(): if mb.user.has_perm("admin.add_domain"): diff --git a/modoboa/limits/app_settings.py b/modoboa/limits/app_settings.py index 3e5ba0dfa..ff729a668 100644 --- a/modoboa/limits/app_settings.py +++ b/modoboa/limits/app_settings.py @@ -8,6 +8,8 @@ from modoboa.lib.parameters import AdminParametersForm class ParametersForm(AdminParametersForm): + """Available admin parameters.""" + app = "limits" defv_sep = SeparatorField(label=_("Default per-admin limits")) @@ -79,17 +81,17 @@ class ParametersForm(AdminParametersForm): help_text=_("Enable or disable per-domain limits") ) - # deflt_domain_domain_admins_limit = forms.IntegerField( - # label=_("Domain admins"), - # initial=0, - # help_text=_( - # "Maximum number of allowed domain administrators for a new " - # "domain. (0 to deny any creation, -1 to allow unlimited " - # "creations)" - # ), - # widget=forms.widgets.TextInput( - # attrs={"class": "col-md-1 form-control"}) - # ) + deflt_domain_domain_admins_limit = forms.IntegerField( + label=_("Domain admins"), + initial=0, + help_text=_( + "Maximum number of allowed domain administrators for a new " + "domain. (0 to deny any creation, -1 to allow unlimited " + "creations)" + ), + widget=forms.widgets.TextInput( + attrs={"class": "col-md-1 form-control"}) + ) deflt_domain_domain_aliases_limit = forms.IntegerField( label=_("Domain aliases"), initial=0, @@ -131,6 +133,7 @@ class ParametersForm(AdminParametersForm): "deflt_domain_mailboxes_limit": "enable_domain_limits=yes", "deflt_domain_mailbox_aliases_limit": "enable_domain_limits=yes", "deflt_domain_domain_aliases_limit": "enable_domain_limits=yes", + "deflt_domain_domain_admins_limit": "enable_domain_limits=yes", } def __init__(self, *args, **kwargs): diff --git a/modoboa/limits/constants.py b/modoboa/limits/constants.py index 4150911df..7bdb3f38d 100644 --- a/modoboa/limits/constants.py +++ b/modoboa/limits/constants.py @@ -43,5 +43,9 @@ DEFAULT_DOMAIN_LIMITS = collections.OrderedDict(( "help": _( "Maximum number of mailbox aliases allowed for this domain."), "extra_filters": {"internal": False} + }), + ("domain_admins", { + "relation": "admins", "label": _("Domain admins"), + "help": _("Maximum number of domain admins allowed for this domain."), }) )) diff --git a/modoboa/limits/controls.py b/modoboa/limits/controls.py index bec50673e..fa914072f 100644 --- a/modoboa/limits/controls.py +++ b/modoboa/limits/controls.py @@ -5,7 +5,7 @@ """ -from modoboa.lib import events +from modoboa.lib import events, parameters from modoboa.lib.permissions import get_object_owner from . import utils @@ -39,7 +39,10 @@ def user_can_set_role(user, role, account=None): :param ``User`` account: account modified (None on creation) :param str newrole: role to check """ - if role != "DomainAdmins": + condition = ( + parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no" or + role != "DomainAdmins") + if condition: return [True] lname = "domain_admins" condition = ( diff --git a/modoboa/limits/general_callbacks.py b/modoboa/limits/general_callbacks.py index 068b9abd9..3d7ab6780 100644 --- a/modoboa/limits/general_callbacks.py +++ b/modoboa/limits/general_callbacks.py @@ -12,7 +12,10 @@ from . import utils def display_pool_usage(user, target, currentpage): from django.template.loader import render_to_string - if target != "leftcol" or user.is_superuser: + condition = ( + parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no" or + target != "leftcol" or user.is_superuser) + if condition: return [] if currentpage == "identities": names = ["mailboxes", "mailbox_aliases"] @@ -38,6 +41,8 @@ def display_pool_usage(user, target, currentpage): @events.observe("ExtraAccountForm") def extra_account_form(user, account=None): + if parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no": + return [] if user.group not in ["SuperAdmins", "Resellers"]: return [] if account is not None and \ @@ -85,16 +90,21 @@ def check_form_access(account, form): @events.observe("FillAccountInstances") def fill_account_instances(user, account, instances): - if not user.is_superuser and not user.belongs_to_group("Resellers"): + condition = ( + parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no" or + (not user.is_superuser and user.group != "Resellers") + ) + if condition: return - if not account.belongs_to_group("Resellers") and \ - not account.belongs_to_group("DomainAdmins"): + if account.group not in ["Resellers", "DomainAdmins"]: return instances["resources"] = account @events.observe("GetStaticContent") def get_static_content(caller, st_type, user): + if parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no": + return [] if caller not in ['domains', 'identities']: return [] if user.group == "SimpleUsers": diff --git a/modoboa/limits/tests/test_domain_limits.py b/modoboa/limits/tests/test_domain_limits.py index 4c18078af..12be480f2 100644 --- a/modoboa/limits/tests/test_domain_limits.py +++ b/modoboa/limits/tests/test_domain_limits.py @@ -3,8 +3,9 @@ from django.core.urlresolvers import reverse -from modoboa.admin.factories import populate_database +from modoboa.admin import factories as admin_factories from modoboa.admin.models import Domain +from modoboa.core import factories as core_factories from modoboa.core.models import User from modoboa.lib import parameters from modoboa.lib import tests as lib_tests @@ -19,11 +20,12 @@ class DomainLimitsTestCase(lib_tests.ModoTestCase): def setUpTestData(cls): """Create test data.""" super(DomainLimitsTestCase, cls).setUpTestData() + parameters.save_admin("ENABLE_ADMIN_LIMITS", "no") parameters.save_admin("ENABLE_DOMAIN_LIMITS", "yes") for name, tpl in utils.get_domain_limit_templates(): parameters.save_admin( "DEFLT_DOMAIN_{}_LIMIT".format(name.upper()), 2) - populate_database() + admin_factories.populate_database() def test_set_limits(self): """Try to set limits for a given domain.""" @@ -32,7 +34,7 @@ class DomainLimitsTestCase(lib_tests.ModoTestCase): "name": domain.name, "quota": domain.quota, "enabled": domain.enabled, "type": "domain", "mailboxes_limit": 3, "mailbox_aliases_limit": 3, - "domain_aliases_limit": 3 + "domain_aliases_limit": 3, "domain_admins_limit": 3 } self.ajax_post( reverse("admin:domain_change", args=[domain.id]), @@ -47,6 +49,9 @@ class DomainLimitsTestCase(lib_tests.ModoTestCase): self.assertEqual( domain.domainobjectlimit_set.get( name="domain_aliases").max_value, 3) + self.assertEqual( + domain.domainobjectlimit_set.get( + name="domain_admins").max_value, 3) def test_domain_aliases_limit(self): """Try to exceed defined limit.""" @@ -57,7 +62,7 @@ class DomainLimitsTestCase(lib_tests.ModoTestCase): "name": domain.name, "quota": domain.quota, "enabled": domain.enabled, "type": "domain", "mailboxes_limit": 2, "mailbox_aliases_limit": 2, - "domain_aliases_limit": 2, + "domain_aliases_limit": 2, "domain_admins_limit": 2, "aliases": "alias1.com", "aliases_1": "alias2.com" } self.ajax_post( @@ -71,6 +76,30 @@ class DomainLimitsTestCase(lib_tests.ModoTestCase): values, 403 ) + def test_domain_admins_limit(self): + """Try to exceed defined limit.""" + domain = Domain.objects.get(name="test.com") + limit = domain.domainobjectlimit_set.get(name="domain_admins") + self.assertFalse(limit.is_exceeded()) + user = User.objects.get(username="admin@test2.com") + values = { + "username": user.username, "role": user.group, + "is_active": user.is_active, "email": user.email, + "quota_act": True, "domains": "test2.com", + "domains_1": "test.com" + } + self.ajax_post( + reverse("admin:account_change", args=[user.id]), + values + ) + self.assertTrue(limit.is_exceeded()) + user = core_factories.UserFactory( + username="admin1000@test.com", groups=("DomainAdmins", )) + self.ajax_post( + reverse("admin:account_change", args=[user.id]), + values, 400 + ) + def test_mailboxes_limit(self): """Try to exceed defined limits.""" domain = Domain.objects.get(name="test.com") diff --git a/modoboa/limits/tests/test_import.py b/modoboa/limits/tests/test_import.py index d88d72483..e58139a1b 100644 --- a/modoboa/limits/tests/test_import.py +++ b/modoboa/limits/tests/test_import.py @@ -50,15 +50,20 @@ domainalias; domalias2.com; {domain}; True ) self.assertContains(response, "Domain aliases: limit reached") - def _test_domain_admins_import(self, limit): + def _test_domain_admins_import(self, limit, initial_count=0): """Check domain admins limit.""" self.client.login( username=self.reseller.username, password="toto") self.assertFalse(limit.is_exceeded()) - f = ContentFile(b"""account; admin1@{domain}; toto; User; One; True; DomainAdmins; user1@{domain}; 5; {domain} -account; admin2@{domain}; toto; René; Truc; True; DomainAdmins; truc@{domain}; 5; {domain} -""".format(domain=self.domain), name="domain_admins.csv") - self.client.post( + content = "" + for cpt in xrange(initial_count, 2): + content += ( + "account; admin{cpt}@{domain}; toto; User; One; True; " + "DomainAdmins; user{cpt}@{domain}; 5; {domain}\n" + .format(domain=self.domain, cpt=cpt) + ) + f = ContentFile(content, name="domain_admins.csv") + response = self.client.post( reverse("admin:identity_import"), { "sourcefile": f } @@ -197,7 +202,6 @@ class DomainLimitImportTestCase(LimitImportTestCase): user__username="user@test4.com", domain__name="test4.com", address="user") cls.domain = mb.domain - cls.domain.add_admin(cls.dadmin) def test_domain_alias_import(self): """Check domain aliases limit.""" @@ -206,10 +210,12 @@ class DomainLimitImportTestCase(LimitImportTestCase): def test_mailboxes_import(self): """Check mailboxes limit.""" + self.domain.add_admin(self.dadmin) limit = self.domain.domainobjectlimit_set.get(name="mailboxes") self._test_mailboxes_import(limit) def test_mailbox_aliases_import(self): """Check mailbox aliases limit.""" + self.domain.add_admin(self.dadmin) limit = self.domain.domainobjectlimit_set.get(name="mailbox_aliases") self._test_mailbox_aliases_import(limit)