diff --git a/modoboa/core/__init__.py b/modoboa/core/__init__.py
index 8e81e7145..751e84e47 100644
--- a/modoboa/core/__init__.py
+++ b/modoboa/core/__init__.py
@@ -39,8 +39,13 @@ base_events = [
]
-def load_settings():
- from .app_settings import GeneralParametersForm, UserSettings
+def load_core_settings():
+ """Load core settings.
+
+ This function must be manually called (see :file:`urls.py`) in
+ order to load base settings.
+ """
+ from modoboa.core.app_settings import GeneralParametersForm, UserSettings
parameters.register(GeneralParametersForm, ugettext_lazy("General"))
parameters.register(UserSettings, ugettext_lazy("General"))
diff --git a/modoboa/core/extensions.py b/modoboa/core/extensions.py
index ecd65de4a..b1f81e9d8 100644
--- a/modoboa/core/extensions.py
+++ b/modoboa/core/extensions.py
@@ -37,7 +37,7 @@ class ExtensionsPool(object):
"""The extensions manager"""
def __init__(self):
- self.extensions = dict()
+ self.extensions = {}
def register_extension(self, ext, show=True):
self.extensions[ext.name] = dict(cls=ext, show=show)
@@ -63,6 +63,16 @@ class ExtensionsPool(object):
return instance.infos()
def load_all(self):
+ """Load all enabled extensions.
+
+ Each extension must be loaded in order to integrate with
+ Modoboa. Only enabled and special extensions are loaded but
+ urls are always returned. The reason is urls are imported only
+ once so must know all of them when the python process
+ starts. Otherwise, it would lead to unexpected 404 errors :p
+
+ :return: a list of url maps
+ """
from modoboa.core.models import Extension
for ext in settings.MODOBOA_APPS:
@@ -70,6 +80,14 @@ class ExtensionsPool(object):
result = []
for extname in self.extensions.keys():
extinstance = self.get_extension(extname)
+ try:
+ baseurl = extinstance.url \
+ if extinstance.url is not None else extname
+ result += [(r'^%s/' % (baseurl),
+ include("%s.urls" % extinstance.__module__))]
+ except ImportError:
+ # No urls for this extension
+ pass
if not extinstance.always_active:
try:
ext = Extension.objects.get(name=extname)
@@ -78,16 +96,11 @@ class ExtensionsPool(object):
except Extension.DoesNotExist:
continue
extinstance.load()
- try:
- baseurl = extinstance.url if extinstance.url is not None else extname
- result += [(r'^%s/' % (baseurl),
- include("%s.urls" % extinstance.__module__))]
- except ImportError:
- # No urls for this extension
- pass
return result
def list_all(self):
+ """List all defined extensions.
+ """
result = []
for extname, extdef in self.extensions.iteritems():
if not extdef["show"]:
diff --git a/modoboa/extensions/admin/__init__.py b/modoboa/extensions/admin/__init__.py
index 281fac420..0589b630b 100644
--- a/modoboa/extensions/admin/__init__.py
+++ b/modoboa/extensions/admin/__init__.py
@@ -13,6 +13,7 @@ admin_events = [
"DomainCreated",
"DomainModified",
"DomainDeleted",
+ "DomainOwnershipRemoved",
"ExtraDomainEntries",
"ExtraDomainMenuEntries",
"ExtraDomainFilters",
@@ -201,6 +202,11 @@ def user_logged_in(request, username, password):
@events.observe("AccountDeleted")
def account_deleted(account, byuser, **kwargs):
+ """'AccountDeleted' listener.
+
+ When an account is deleted, we also need to remove its mailbox (if
+ any).
+ """
if not account.mailbox_set.count():
return
mb = account.mailbox_set.all()[0]
diff --git a/modoboa/extensions/admin/forms/account.py b/modoboa/extensions/admin/forms/account.py
index c8afb5fae..a44ba884c 100644
--- a/modoboa/extensions/admin/forms/account.py
+++ b/modoboa/extensions/admin/forms/account.py
@@ -46,20 +46,20 @@ class AccountFormGeneral(forms.ModelForm):
widget=forms.HiddenInput, required=False
)
else:
- self.fields["role"].choices = \
- [('', ugettext_lazy("Choose"))] + get_account_roles(user)
+ self.fields["role"].choices = [('', ugettext_lazy("Choose"))]
+ self.fields["role"].choices += \
+ get_account_roles(user, kwargs['instance']) \
+ if 'instance' in kwargs else get_account_roles(user)
if "instance" in kwargs:
- if len(args) \
+ if args \
and (args[0].get("password1", "") == ""
and args[0].get("password2", "") == ""):
self.fields["password1"].required = False
self.fields["password2"].required = False
-
- u = kwargs["instance"]
- self.fields["role"].initial = u.group
-
- if not u.is_local \
+ account = kwargs["instance"]
+ self.fields["role"].initial = account.group
+ if not account.is_local \
and parameters.get_admin("LDAP_AUTH_METHOD") == "directbind":
del self.fields["password1"]
del self.fields["password2"]
diff --git a/modoboa/extensions/admin/models/domain.py b/modoboa/extensions/admin/models/domain.py
index b0a2c474e..70166b9b4 100644
--- a/modoboa/extensions/admin/models/domain.py
+++ b/modoboa/extensions/admin/models/domain.py
@@ -23,6 +23,8 @@ class DomainManager(Manager):
class Domain(AdminObject):
+ """Mail domain.
+ """
name = models.CharField(ugettext_lazy('name'), max_length=100, unique=True,
help_text=ugettext_lazy("The domain name"))
quota = models.IntegerField(
@@ -84,11 +86,15 @@ class Domain(AdminObject):
grant_access_to_object(account, al)
def remove_admin(self, account):
- """Remove an administrator for this domain
+ """Remove an administrator of this domain.
- :param User account: the administrotor to remove
+ :param User account: administrator to remove
"""
- from modoboa.lib.permissions import ungrant_access_to_object
+ from modoboa.lib.permissions import \
+ ungrant_access_to_object, get_object_owner
+
+ if get_object_owner(self) == account:
+ events.raiseEvent('DomainOwnershipRemoved', account, self)
ungrant_access_to_object(self, account)
for mb in self.mailbox_set.all():
if mb.user.has_perm("admin.add_domain"):
@@ -96,26 +102,27 @@ class Domain(AdminObject):
ungrant_access_to_object(mb, account)
ungrant_access_to_object(mb.user, account)
for al in self.alias_set.all():
- ungrant_access_to_object(al, account)
+ ungrant_access_to_object(al, account)
def delete(self, fromuser, keepdir=False):
- from modoboa.lib.permissions import \
- ungrant_access_to_object, ungrant_access_to_objects
+ """Custom delete method.
+ """
+ from modoboa.lib.permissions import ungrant_access_to_objects
from .mailbox import Quota
if self.domainalias_set.count():
events.raiseEvent("DomainAliasDeleted", self.domainalias_set.all())
ungrant_access_to_objects(self.domainalias_set.all())
- if self.mailbox_set.count():
- Quota.objects.filter(username__contains='@%s' % self.name).delete()
- events.raiseEvent("DeleteMailbox", self.mailbox_set.all())
- ungrant_access_to_objects(self.mailbox_set.all())
if self.alias_set.count():
- events.raiseEvent("MailboxAliasDelete", self.alias_set.all())
+ events.raiseEvent("MailboxAliasDeleted", self.alias_set.all())
ungrant_access_to_objects(self.alias_set.all())
if parameters.get_admin("AUTO_ACCOUNT_REMOVAL") == "yes":
for account in User.objects.filter(mailbox__domain__name=self.name):
account.delete(fromuser, keepdir)
+ elif self.mailbox_set.count():
+ Quota.objects.filter(username__contains='@%s' % self.name).delete()
+ events.raiseEvent("MailboxDeleted", self.mailbox_set.all())
+ ungrant_access_to_objects(self.mailbox_set.all())
super(Domain, self).delete()
def __str__(self):
diff --git a/modoboa/extensions/admin/models/mailbox.py b/modoboa/extensions/admin/models/mailbox.py
index a16e478a0..ed61adcbe 100644
--- a/modoboa/extensions/admin/models/mailbox.py
+++ b/modoboa/extensions/admin/models/mailbox.py
@@ -238,7 +238,7 @@ def mailbox_deleted_handler(sender, **kwargs):
from modoboa.lib.permissions import ungrant_access_to_object
mb = kwargs['instance']
- events.raiseEvent("DeleteMailbox", mb)
+ events.raiseEvent("MailboxDeleted", mb)
ungrant_access_to_object(mb)
for alias in mb.alias_set.all():
alias.mboxes.remove(mb)
diff --git a/modoboa/extensions/admin/static/admin/js/admin.js b/modoboa/extensions/admin/static/admin/js/admin.js
index 57f27f9be..d0a5ef387 100644
--- a/modoboa/extensions/admin/static/admin/js/admin.js
+++ b/modoboa/extensions/admin/static/admin/js/admin.js
@@ -213,11 +213,11 @@ Domains.prototype = {
domadminsform_init: function() {
$("a[name=removeperm]").click(function(e) {
- var $tr = $(this).parent().parent();
+ var $tr = $(this).parents('tr');
simple_ajax_request.apply(this, [e, {
ok_cb: function(resp) {
$tr.remove();
- if (!$("#domadmins").children("tr").length) {
+ if (!$("#domadmins").find("tr").length) {
$("#admins").html('
'
+ gettext("No domain administrator defined") + "
");
}
@@ -235,7 +235,6 @@ Domains.prototype = {
1: this.optionsform_prefill
},
error_callbacks: {
- //1: this.generalform_init,
2: $.proxy(this.optionsform_init, this)
},
success_callback: $.proxy(this.reload_listing, this)
@@ -254,7 +253,7 @@ Domains.prototype = {
});
}, this));
$(document).trigger('domform_init');
- },
+ }
};
Domains.prototype = $.extend({}, Admin.prototype, Domains.prototype);
diff --git a/modoboa/extensions/limits/__init__.py b/modoboa/extensions/limits/__init__.py
index 7f49c549f..61a6961fe 100644
--- a/modoboa/extensions/limits/__init__.py
+++ b/modoboa/extensions/limits/__init__.py
@@ -12,7 +12,6 @@ from modoboa.lib import events, parameters
from modoboa.core.extensions import ModoExtension, exts_pool
from .models import LimitTemplates
from .forms import ResourcePoolForm
-import controls
levents = [
'GetExtraLimitTemplates'
@@ -53,6 +52,8 @@ class Limits(ModoExtension):
def load(self):
from modoboa.extensions.limits.app_settings import ParametersForm
+ from modoboa.extensions.limits import controls
+
parameters.register(ParametersForm, ugettext_lazy("Limits"))
events.declare(levents)
diff --git a/modoboa/extensions/limits/controls.py b/modoboa/extensions/limits/controls.py
index 7ddd2f212..34ff09159 100644
--- a/modoboa/extensions/limits/controls.py
+++ b/modoboa/extensions/limits/controls.py
@@ -56,8 +56,9 @@ def inc_nb_domains(user, domain):
@events.observe('DomainDeleted')
-def dec_nb_domains(domain):
- owner = get_object_owner(domain)
+def dec_nb_domains(domain, owner=None):
+ if owner is None:
+ owner = get_object_owner(domain)
dec_limit_usage(owner, 'domains_limit')
for domalias in domain.domainalias_set.all():
dec_nb_domaliases(domalias)
@@ -105,9 +106,14 @@ def inc_nb_mbaliases(user, mailboxalias):
@events.observe('MailboxAliasDeleted')
-def dec_nb_mbaliases(mailboxalias):
- owner = get_object_owner(mailboxalias)
- dec_limit_usage(owner, 'mailbox_aliases_limit')
+def dec_nb_mbaliases(mailboxaliases):
+ from modoboa.extensions.admin.models import Alias
+
+ if isinstance(mailboxaliases, Alias):
+ mailboxaliases = [mailboxaliases]
+ for alias in mailboxaliases:
+ owner = get_object_owner(alias)
+ dec_limit_usage(owner, 'mailbox_aliases_limit')
@events.observe('CanCreate')
@@ -118,21 +124,20 @@ def can_create_new_object(user, objtype, count=1):
@events.observe("AccountCreated")
def create_pool(user):
owner = get_object_owner(user)
- if not owner.is_superuser and \
- not owner.belongs_to_group("Resellers"):
+ if not owner.group in ['SuperAdmins', 'Resellers']:
return
- if user.belongs_to_group("DomainAdmins"):
+ if user.group == 'DomainAdmins':
check_limit(owner, 'domain_admins_limit')
inc_limit_usage(owner, 'domain_admins_limit')
- if user.group in ["DomainAdmins", "Resellers"]:
+ if user.group in ['DomainAdmins', 'Resellers']:
p, created = LimitsPool.objects.get_or_create(user=user)
p.create_limits(owner)
@events.observe("UserCanSetRole")
-def user_can_set_role(user, role):
+def user_can_set_role(user, role, account=None):
"""Check if the user can still set this role.
The only interesting case concerns resellers defining new domain
@@ -140,6 +145,7 @@ def user_can_set_role(user, role):
operation before any modification is made to :keyword:`account`.
:param ``User`` user: connected user
+ :param ``User`` account: account modified (None on creation)
:param str newrole: role to check
"""
if role == 'DomainAdmins':
@@ -148,6 +154,8 @@ def user_can_set_role(user, role):
return [True]
if user.is_superuser or not user.limitspool.will_be_reached(lname):
return [True]
+ if account is not None and account.group == role:
+ return [True]
return [False]
@@ -192,3 +200,20 @@ def on_account_deleted(account, byuser, **kwargs):
if account.group == "DomainAdmins":
dec_limit_usage(owner, 'domain_admins_limit')
+
+
+@events.observe('DomainOwnershipRemoved')
+def domain_ownership_removed(reseller, domain):
+ """DomainOwnershipRemoved listener.
+
+ The access :keyword:`reseller` had to :keyword:`domain` has been
+ removed by a super adminstrator. We must decrement all limit
+ usages.
+
+ :param ``User`` reseller: reseller that created :keyword:`domain`
+ :param ``Domain`` domain: domain
+ """
+ dec_nb_domains(domain, reseller)
+ for dadmin in domain.admins:
+ if reseller.is_owner(dadmin):
+ dec_limit_usage(reseller, 'domain_admins_limit')
diff --git a/modoboa/extensions/limits/tests.py b/modoboa/extensions/limits/tests.py
index 0bae19785..ccef3e3b6 100644
--- a/modoboa/extensions/limits/tests.py
+++ b/modoboa/extensions/limits/tests.py
@@ -12,16 +12,11 @@ from modoboa.extensions.limits.models import LimitTemplates
class PermissionsTestCase(ExtTestCase):
fixtures = ["initial_users.json"]
- name = "limits"
+ names = ["limits"]
def setUp(self):
super(PermissionsTestCase, self).setUp()
- dom = DomainFactory(name='test.com')
- self.admin = UserFactory(
- username='admin@test.com', groups=('DomainAdmins',)
- )
- MailboxFactory(address='admin', domain=dom, user=self.admin)
- dom.add_admin(self.admin)
+ populate_database()
def test_domainadmin_deletes_reseller(self):
"""Check if a domain admin can delete a reseller.
@@ -46,13 +41,21 @@ class PermissionsTestCase(ExtTestCase):
class ResourceTestCase(ExtTestCase):
fixtures = ["initial_users.json"]
- name = "limits"
+ names = ["limits"]
def setUp(self):
+ """Custom setUp method.
+
+ The 'limits' is manually loaded to ensure extra parameters
+ provided by 'postfix_relay_domains' are properly received.
+ """
+ from modoboa.core.extensions import exts_pool
+
super(ResourceTestCase, self).setUp()
- populate_database()
+ exts_pool.get_extension('limits').load()
for tpl in LimitTemplates().templates:
parameters.save_admin('DEFLT_%s' % tpl[0].upper(), 2, app='limits')
+ populate_database()
def _create_account(self, username, role='SimpleUsers', status=200):
values = dict(
@@ -255,6 +258,37 @@ class ResellerTestCase(ResourceTestCase):
self._check_limit('domain_admins', 2, 2)
self._check_limit('domains', 3, 3)
+ def test_reseller_deletes_domain(self):
+ """Check if all resources are restored after the deletion.
+ """
+ self._create_domain('domain.tld', withtpl=True)
+ dom = Domain.objects.get(name="domain.tld")
+ self.ajax_post(
+ reverse("modoboa.extensions.admin.views.domain.deldomain",
+ args=[dom.id]),
+ {}
+ )
+ self._check_limit('domains', 0, 2)
+ self._check_limit('domain_admins', 1, 2)
+ self._check_limit('mailboxes', 0, 2)
+ self._check_limit('mailbox_aliases', 0, 2)
+
+ def test_sadmin_removes_ownership(self):
+ self._create_domain('domain.tld', withtpl=True)
+ dom = Domain.objects.get(name="domain.tld")
+ self.clt.logout()
+ self.clt.login(username='admin', password='password')
+ self.ajax_get(
+ reverse(
+ 'modoboa.extensions.admin.views.identity.remove_permission'
+ ) + '?domid=%d&daid=%d' % (dom.id, self.user.id)
+ , {}
+ )
+ self._check_limit('domains', 0, 2)
+ self._check_limit('domain_admins', 0, 2)
+ self._check_limit('mailboxes', 0, 2)
+ self._check_limit('mailbox_aliases', 0, 2)
+
def test_allocate_from_pool(self):
self._create_domain('domain.tld')
self._create_account('admin1@domain.tld', role='DomainAdmins')
diff --git a/modoboa/extensions/postfix_relay_domains/__init__.py b/modoboa/extensions/postfix_relay_domains/__init__.py
index 647615e24..da9bf2ea9 100644
--- a/modoboa/extensions/postfix_relay_domains/__init__.py
+++ b/modoboa/extensions/postfix_relay_domains/__init__.py
@@ -23,6 +23,7 @@ class PostfixRelayDomains(ModoExtension):
label = "Postfix relay domains"
version = "1.0"
description = ugettext_lazy("Relay domains support for Postfix")
+ depends_on = ['limits']
def init(self):
"""Initialisation method.
diff --git a/modoboa/extensions/postfix_relay_domains/factories.py b/modoboa/extensions/postfix_relay_domains/factories.py
new file mode 100644
index 000000000..c8bc6e1c5
--- /dev/null
+++ b/modoboa/extensions/postfix_relay_domains/factories.py
@@ -0,0 +1,23 @@
+import factory
+from modoboa.core.factories import PermissionFactory
+from . import models
+
+
+class ServiceFactory(factory.DjangoModelFactory):
+ FACTORY_FOR = models.Service
+ FACTORY_DJANGO_GET_OR_CREATE = ('name', )
+
+ name = 'dummy'
+
+
+class RelayDomainFactory(PermissionFactory):
+ FACTORY_FOR = models.RelayDomain
+
+ target_host = 'external.host.tld'
+ service = factory.SubFactory(ServiceFactory)
+
+
+class RelayDomainAliasFactory(PermissionFactory):
+ FACTORY_FOR = models.RelayDomainAlias
+
+ enabled = True
diff --git a/modoboa/extensions/postfix_relay_domains/tests.py b/modoboa/extensions/postfix_relay_domains/tests.py
index dab97161c..1361a757a 100644
--- a/modoboa/extensions/postfix_relay_domains/tests.py
+++ b/modoboa/extensions/postfix_relay_domains/tests.py
@@ -1,13 +1,201 @@
+from django.core.urlresolvers import reverse
+from modoboa.core.factories import UserFactory
from modoboa.lib.tests import ExtTestCase
from modoboa.extensions.admin import factories
+from modoboa.extensions.limits.tests import ResourceTestCase
+from .models import RelayDomain, RelayDomainAlias, Service
+from .factories import RelayDomainFactory, RelayDomainAliasFactory
-class RelayDomainsTestCase(ExtTestCase):
- fixtures = ["initial_users.json"]
- name = "postfix_relay_domains"
+class Operations(object):
+
+ def _create_relay_domain(self, name, status=200):
+ srv, created = Service.objects.get_or_create(name='dummy')
+ values = {
+ 'name': name, 'target_host': 'external.host.tld',
+ 'service': srv.id, 'enabled': True
+ }
+ return self.ajax_post(
+ reverse("modoboa.extensions.postfix_relay_domains.views.create"),
+ values, status
+ )
+
+ def _relay_domain_alias_operation(self, optype, rdomain, name, status=200):
+ rdom = RelayDomain.objects.get(name=rdomain)
+ values = {
+ 'name': rdom.name, 'target_host': rdom.target_host,
+ 'service': rdom.service.id
+ }
+ aliases = [alias.name for alias in rdom.relaydomainalias_set.all()]
+ if optype == 'add':
+ aliases.append(name)
+ else:
+ aliases.remove(name)
+ for cpt, alias in enumerate(aliases):
+ fname = 'aliases' if not cpt else 'aliases_%d' % cpt
+ values[fname] = alias
+ return self.ajax_post(
+ reverse("modoboa.extensions.postfix_relay_domains.views.edit",
+ args=[rdom.id]),
+ values, status
+ )
+
+
+class RelayDomainsTestCase(ExtTestCase, Operations):
+ fixtures = ['initial_users.json']
+ names = ['postfix_relay_domains']
def setUp(self):
super(RelayDomainsTestCase, self).setUp()
factories.populate_database()
+ self.rdom = RelayDomainFactory(name='relaydomain.tld')
+ RelayDomainAliasFactory(name='relaydomainalias.tld', target=self.rdom)
-
+ def test_create_relaydomain(self):
+ """Test the creation of a relay domain.
+
+ We also check that unique constraints are respected: domain,
+ relay domain alias.
+
+ FIXME: add a check for domain alias.
+ """
+ self._create_relay_domain('relaydomain1.tld')
+ rdom = RelayDomain.objects.get(name='relaydomain1.tld')
+ self.assertEqual(rdom.target_host, 'external.host.tld')
+ self.assertEqual(rdom.service.name, 'dummy')
+ self.assertEqual(rdom.enabled, True)
+ self.assertEqual(rdom.verify_recipients, False)
+
+ resp = self._create_relay_domain('test.com', 400)
+ self.assertEqual(resp['form_errors']['name'][0],
+ 'A domain with this name already exists')
+ resp = self._create_relay_domain('relaydomainalias.tld', 400)
+ self.assertEqual(
+ resp['form_errors']['name'][0],
+ 'A relay domain alias with this name already exists'
+ )
+
+ def test_create_relaydomainalias(self):
+ """Test the creation of a relay domain alias.
+
+ We also check that unique constraints are respected: domain,
+ relay domain.
+
+ FIXME: add a check for domain alias.
+ """
+ self._relay_domain_alias_operation(
+ 'add', self.rdom.name, 'relaydomainalias1.tld'
+ )
+ resp = self._relay_domain_alias_operation(
+ 'add', self.rdom.name, 'test.com', 400
+ )
+ self.assertEqual(
+ resp['form_errors']['aliases_2'][0],
+ 'A domain with this name already exists'
+ )
+ resp = self._relay_domain_alias_operation(
+ 'add', self.rdom.name, self.rdom.name, 400
+ )
+ self.assertEqual(
+ resp['form_errors']['aliases_2'][0],
+ 'A relay domain with this name already exists'
+ )
+
+ def test_edit_relaydomain(self):
+ """Test the modification of a relay domain.
+
+ Rename 'relaydomain.tld' domain to 'relaydomain.org'
+ """
+ values = {
+ 'name': 'relaydomain.org', 'target_host': self.rdom.target_host,
+ 'service': self.rdom.service.id
+ }
+ self.ajax_post(
+ reverse('modoboa.extensions.postfix_relay_domains.views.edit',
+ args=[self.rdom.id]),
+ values
+ )
+ RelayDomain.objects.get(name='relaydomain.org')
+
+ def test_edit_relaydomainalias(self):
+ """Test the modification of a relay domain alias.
+
+ Rename 'relaydomainalias.tld' domain to 'relaydomainalias.net'
+ """
+ values = {
+ 'name': 'relaydomain.org', 'target_host': self.rdom.target_host,
+ 'service': self.rdom.service.id, 'aliases': 'relaydomainalias.net'
+ }
+ self.ajax_post(
+ reverse('modoboa.extensions.postfix_relay_domains.views.edit',
+ args=[self.rdom.id]),
+ values
+ )
+ RelayDomainAlias.objects.get(name='relaydomainalias.net')
+ with self.assertRaises(RelayDomainAlias.DoesNotExist):
+ RelayDomainAlias.objects.get(name='relaydomainalias.tld')
+
+ def test_delete_relaydomain(self):
+ """Test the removal of a relay domain
+ """
+ self.ajax_post(
+ reverse("modoboa.extensions.postfix_relay_domains.views.delete",
+ args=[self.rdom.id]),
+ {}
+ )
+ with self.assertRaises(RelayDomain.DoesNotExist):
+ RelayDomain.objects.get(name='relaydomain.tld')
+
+ def test_delete_relaydomainalias(self):
+ """Test the remove of a relay domain alias
+ """
+ self._relay_domain_alias_operation(
+ 'del', self.rdom.name, 'relaydomainalias.tld'
+ )
+ with self.assertRaises(RelayDomain.DoesNotExist):
+ RelayDomain.objects.get(name='relaydomainalias.tld')
+
+
+class LimitsTestCase(ResourceTestCase, Operations):
+ names = ['limits', 'postfix_relay_domains']
+
+ def setUp(self):
+ super(LimitsTestCase, self).setUp()
+ self.user = UserFactory(
+ username='reseller', groups=('Resellers',)
+ )
+ self.clt.logout()
+ self.clt.login(username='reseller', password='toto')
+
+ def test_relay_domains_limit(self):
+ self._create_relay_domain('relaydomain1.tld')
+ self._check_limit('relay_domains', 1, 2)
+ self._create_relay_domain('relaydomain2.tld')
+ self._check_limit('relay_domains', 2, 2)
+ resp = self._create_relay_domain('relaydomain3.tld', 403)
+ self._check_limit('relay_domains', 2, 2)
+ self.ajax_post(
+ reverse('modoboa.extensions.postfix_relay_domains.views.delete',
+ args=[RelayDomain.objects.get(name='relaydomain2.tld').id]),
+ {}
+ )
+ self._check_limit('relay_domains', 1, 2)
+
+ def test_relay_domain_aliases_limit(self):
+ self._create_relay_domain('relaydomain1.tld')
+ self._relay_domain_alias_operation(
+ 'add', 'relaydomain1.tld', 'relay_domain_alias1.tld'
+ )
+ self._check_limit('relay_domain_aliases', 1, 2)
+ self._relay_domain_alias_operation(
+ 'add', 'relaydomain1.tld', 'relay_domain_alias2.tld'
+ )
+ self._check_limit('relay_domain_aliases', 2, 2)
+ self._relay_domain_alias_operation(
+ 'add', 'relaydomain1.tld', 'relay_domain_alias3.tld', 403
+ )
+ self._check_limit('relay_domain_aliases', 2, 2)
+ self._relay_domain_alias_operation(
+ 'delete', 'relaydomain1.tld', 'relay_domain_alias2.tld'
+ )
+ self._check_limit('relay_domain_aliases', 1, 2)
diff --git a/modoboa/lib/permissions.py b/modoboa/lib/permissions.py
index 8b93a2d8c..5cf677aaf 100644
--- a/modoboa/lib/permissions.py
+++ b/modoboa/lib/permissions.py
@@ -2,23 +2,27 @@
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _
from django.db import IntegrityError
-from modoboa.core.models import ObjectAccess
-import events
-from exceptions import ModoboaException
+from modoboa.core.models import ObjectAccess, User
+from modoboa.lib import events
+from modoboa.lib.exceptions import ModoboaException
-def get_account_roles(user):
- """Return the list of supported account roles
+def get_account_roles(user, account=None):
+ """Return the list of available account roles.
- This list can be extended by extensions which listen to the
- ``GetExtraRoles`` event.
+ This function is used to create or modify an account. The returned
+ list can be extended by listening to the ``GetExtraRoles`` event.
+ :param ``User`` user: connected user
+ :param ``User`` account: account beeing modified (None on creation)
:return: list of strings
"""
std_roles = [("SimpleUsers", _("Simple user"))]
if user.is_superuser:
std_roles += [("SuperAdmins", _("Super administrator"))]
- filters = events.raiseQueryEvent('UserCanSetRole', user, 'DomainAdmins')
+ filters = events.raiseQueryEvent(
+ 'UserCanSetRole', user, 'DomainAdmins', account
+ )
if user.has_perm("admin.add_domain") and \
(not filters or True in filters):
std_roles += [("DomainAdmins", _("Domain administrator"))]
@@ -92,17 +96,32 @@ def ungrant_access_to_object(obj, user=None):
If no user is provided, all entries referencing this object are
deleted from the database.
+ If a user is provided, we only remove his access. If it was the
+ owner, we give the ownership to the first super admin we find.
+
:param obj: an object inheriting from ``models.Model``
:param user: a ``User`` object
"""
ct = ContentType.objects.get_for_model(obj)
- if user:
+ if user is not None:
try:
- ObjectAccess.objects.get(user=user, content_type=ct, object_id=obj.id).delete()
+ ObjectAccess.objects.get(
+ user=user, content_type=ct, object_id=obj.id
+ ).delete()
except ObjectAccess.DoesNotExist:
pass
+ try:
+ ObjectAccess.objects.get(
+ content_type=ct, object_id=obj.id, is_owner=True
+ )
+ except ObjectAccess.DoesNotExist:
+ grant_access_to_object(
+ User.objects.filter(is_superuser=True)[0], obj, True
+ )
else:
- ObjectAccess.objects.filter(content_type=ct, object_id=obj.id).delete()
+ ObjectAccess.objects.filter(
+ content_type=ct, object_id=obj.id
+ ).delete()
def ungrant_access_to_objects(objects):
diff --git a/modoboa/lib/tests.py b/modoboa/lib/tests.py
index 8601f8682..3ad5f2007 100644
--- a/modoboa/lib/tests.py
+++ b/modoboa/lib/tests.py
@@ -27,13 +27,15 @@ class ModoTestCase(TestCase):
class ExtTestCase(ModoTestCase):
- name = None
+ names = []
def setUp(self, *args, **kwargs):
super(ExtTestCase, self).setUp(*args, **kwargs)
self.clt.get(reverse("modoboa.core.views.admin.viewextensions"))
- self.clt.post(reverse("modoboa.core.views.admin.saveextensions"),
- {"select_%s" % self.name : "1"})
+ self.clt.post(
+ reverse("modoboa.core.views.admin.saveextensions"),
+ dict(("select_%s" % name, "1") for name in self.names)
+ )
class TestParams(parameters.AdminParametersForm):
diff --git a/modoboa/static/js/creation_wizard.js b/modoboa/static/js/creation_wizard.js
index e10985d64..2f1f5eabf 100644
--- a/modoboa/static/js/creation_wizard.js
+++ b/modoboa/static/js/creation_wizard.js
@@ -42,11 +42,10 @@
type: 'POST', url: $form.attr("action"), data: data, global: false
}).done($.proxy(function(resp) {
if (!last) {
- var stepid = resp.stepid + 1;
$('input:text:visible:first').focus();
this.set_current_title(resp.title);
- if (this.options.transition_callbacks[stepid] != undefined) {
- this.options.transition_callbacks[stepid]();
+ if (this.options.transition_callbacks[resp.stepid] != undefined) {
+ this.options.transition_callbacks[resp.stepid]();
}
this.$element.carousel('next');
} else {
diff --git a/modoboa/static/js/global.js b/modoboa/static/js/global.js
index b899731c0..6aaa76e11 100644
--- a/modoboa/static/js/global.js
+++ b/modoboa/static/js/global.js
@@ -35,15 +35,6 @@ function modalbox(e, css, defhref, defcb, defclosecb) {
type: "GET",
url: href
}).done(function(data) {
- if (typeof data === "object") {
- /*
- * Compatibility with the old way...
- */
- if (data.status == "ko" && data.respmsg) {
- $("body").notify("error", data.respmsg);
- }
- return;
- }
var $div = $('', {
id: "modalbox", 'class': "modal", html: data
});
@@ -56,8 +47,7 @@ function modalbox(e, css, defhref, defcb, defclosecb) {
if (modalcb != undefined) {
if (typeof modalcb === "function") modalcb(); else eval(modalcb + '()');
}
- })
- .on('hidden', function(e) {
+ }).on('hidden', function(e) {
var $target = $(e.target);
if (!$target.is($(this))) {
diff --git a/modoboa/urls.py b/modoboa/urls.py
index f100d17e1..28a89b86b 100644
--- a/modoboa/urls.py
+++ b/modoboa/urls.py
@@ -2,9 +2,9 @@ from django.conf.urls import patterns, include
from django.conf import settings
from modoboa.core.extensions import exts_pool
from modoboa.lib import parameters, events
-from modoboa.core import load_settings
+from modoboa.core import load_core_settings
-load_settings()
+load_core_settings()
urlpatterns = patterns(
'',