From 6f1259aafdb28af8cfa2c7ac8034b4487d447f7f Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 26 Nov 2013 09:55:41 +0100 Subject: [PATCH 1/6] Try to handle errors when connecting to amavis. fixes #257 --- modoboa/lib/dbutils.py | 13 ++++++++++--- modoboa/lib/webutils.py | 7 +++---- modoboa/templates/common/error.html | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/modoboa/lib/dbutils.py b/modoboa/lib/dbutils.py index 12e6ec843..1bcd03fcb 100644 --- a/modoboa/lib/dbutils.py +++ b/modoboa/lib/dbutils.py @@ -1,7 +1,10 @@ # coding: utf-8 from django.db import connection from django.conf import settings - +from django.utils.translation import ugettext as _ +from modoboa.lib.exceptions import InternalError + + def db_table_exists(table, cursor=None): """Check if table exists @@ -20,14 +23,18 @@ def db_table_exists(table, cursor=None): else: return table in table_names -def db_type(connection="default"): + +def db_type(cname="default"): """Return the type of the *default* database Supported values : 'postgres', 'mysql', 'sqlite' + :param str cname: connection name :return: a string or None """ + if not cname in settings.DATABASES: + raise InternalError(_("Connection to database %s not configured" % cname)) for t in ['postgres', 'mysql', 'sqlite']: - if settings.DATABASES[connection]['ENGINE'].find(t) != -1: + if settings.DATABASES[cname]['ENGINE'].find(t) != -1: return t return None diff --git a/modoboa/lib/webutils.py b/modoboa/lib/webutils.py index 843897378..a41cec029 100644 --- a/modoboa/lib/webutils.py +++ b/modoboa/lib/webutils.py @@ -8,7 +8,7 @@ import re import json from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth.decorators import login_required -from django.shortcuts import render_to_response +from django.shortcuts import render, render_to_response from django.template.loader import render_to_string from django import template from django.conf import settings @@ -27,9 +27,8 @@ def _render_to_string(request, tpl, user_context): def _render_error(request, errortpl="error", user_context=None): if user_context is None: user_context = {} - return render_to_response( - "common/%s.html" % errortpl, user_context, - context_instance=template.RequestContext(request) + return render( + request, "common/%s.html" % errortpl, user_context ) diff --git a/modoboa/templates/common/error.html b/modoboa/templates/common/error.html index 0c57125a0..74c95a380 100644 --- a/modoboa/templates/common/error.html +++ b/modoboa/templates/common/error.html @@ -1,4 +1,4 @@ -{% extends "nlayout.html" %} +{% extends "layout_simple.html" %} {% load i18n %} {% block content %}
{% trans "Error:" %} {{ error }}
From 96aacbd4845ca40c78d0674d78ad580e6c66d4f7 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 26 Nov 2013 10:23:52 +0100 Subject: [PATCH 2/6] force html links to open in a new tab. --- modoboa/lib/emailutils.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/modoboa/lib/emailutils.py b/modoboa/lib/emailutils.py index b18e2c48d..0f298d9bd 100644 --- a/modoboa/lib/emailutils.py +++ b/modoboa/lib/emailutils.py @@ -147,10 +147,16 @@ class Email(object): return path def map_cid(self, url): - m = re.match(".*cid:(.+)", url) - if m: - if m.group(1) in self.attached_map: - return self.attached_map[m.group(1)] + """Replace attachment links. + + :param str url: original url + :rtype: string + :return: internal link + """ + match = re.match(".*cid:(.+)", url) + if match is not None: + if match.group(1) in self.attached_map: + return self.attached_map[match.group(1)] return url def render_headers(self, **kwargs): @@ -172,6 +178,8 @@ class Email(object): html.rewrite_links(lambda x: None) else: html.rewrite_links(self.map_cid) + for link in html.iterlinks(): + link[0].set('target', '_blank') body = html.find("body") if body is None: body = lxml.html.tostring(html) From 1d577d96bd7e47e3cd5e82e28c3a6d30fa02f079 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 26 Nov 2013 15:04:05 +0100 Subject: [PATCH 3/6] LDAP improvements: * Support for domain adminstrator groups mapping (fixes #449) * Support for better hash algorithm (see #450) --- modoboa/core/app_settings.py | 33 ++++++++++++++++++++--- modoboa/core/models.py | 6 ++--- modoboa/core/password_hashers/advanced.py | 15 ++++++++--- modoboa/core/password_hashers/base.py | 29 ++++++++++++++++++-- modoboa/extensions/admin/__init__.py | 4 ++- modoboa/extensions/admin/forms/account.py | 2 +- modoboa/lib/authbackends.py | 16 ++++++++--- modoboa/lib/ldaputils.py | 31 +++++---------------- 8 files changed, 94 insertions(+), 42 deletions(-) diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index 0194ba035..66175df6a 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -118,9 +118,9 @@ class GeneralParametersForm(parameters.AdminParametersForm): ) ldap_search_base = forms.CharField( - label=ugettext_lazy("Search base"), + label=ugettext_lazy("Users search base"), initial="", - help_text=ugettext_lazy("The distinguished name of the search base"), + help_text=ugettext_lazy("The distinguished name of the search base used to find users"), required=False ) @@ -150,6 +150,25 @@ class GeneralParametersForm(parameters.AdminParametersForm): help_text=ugettext_lazy("Tell if the LDAP server is an Active Directory one") ) + ldap_admin_groups = forms.CharField( + label=ugettext_lazy("Administrator groups"), + initial="", + help_text=ugettext_lazy( + "Members of those LDAP groups will be created as domain " + "administrators. Use a ';' characters to separate groups" + ), + required=False + ) + + ldap_groups_search_base = forms.CharField( + label=ugettext_lazy("Groups search base"), + initial="", + help_text=ugettext_lazy( + "The distinguished name of the search base used to find groups" + ), + required=False + ) + sep3 = SeparatorField(label=ugettext_lazy("Miscellaneous")) log_maximum_age = forms.IntegerField( @@ -185,6 +204,8 @@ class GeneralParametersForm(parameters.AdminParametersForm): "ldap_user_dn_template": "ldap_auth_method=directbind", "ldap_password_attribute": "authentication_type=ldap", "ldap_is_active_directory": "authentication_type=ldap", + "ldap_admin_groups": "authentication_type=ldap", + "ldap_groups_search_base": "authentication_type=ldap", } def __init__(self, *args, **kwargs): @@ -243,7 +264,7 @@ class GeneralParametersForm(parameters.AdminParametersForm): """ try: import ldap - from django_auth_ldap.config import LDAPSearch + from django_auth_ldap.config import LDAPSearch, PosixGroupType ldap_available = True except ImportError: ldap_available = False @@ -260,6 +281,11 @@ class GeneralParametersForm(parameters.AdminParametersForm): ldap_uri = 'ldaps://' if values["ldap_secured"] == "yes" else "ldap://" ldap_uri += "%s:%s" % (values["ldap_server_address"], values["ldap_server_port"]) setattr(settings, "AUTH_LDAP_SERVER_URI", ldap_uri) + setattr(settings, "AUTH_LDAP_GROUP_TYPE", PosixGroupType()) + setattr(settings, "AUTH_LDAP_GROUP_SEARCH", LDAPSearch( + values["ldap_groups_search_base"], ldap.SCOPE_SUBTREE, + "(objectClass=posixGroup)" + )) if values["ldap_auth_method"] == "searchbind": setattr(settings, "AUTH_LDAP_BIND_DN", values["ldap_bind_dn"]) setattr(settings, "AUTH_LDAP_BIND_PASSWORD", values["ldap_bind_password"]) @@ -270,7 +296,6 @@ class GeneralParametersForm(parameters.AdminParametersForm): setattr(settings, "AUTH_LDAP_USER_SEARCH", search) else: setattr(settings, "AUTH_LDAP_USER_DN_TEMPLATE", values["ldap_user_dn_template"]) - if values["ldap_is_active_directory"] == "yes": if not hasattr(settings, "AUTH_LDAP_GLOBAL_OPTIONS"): setattr(settings, "AUTH_LDAP_GLOBAL_OPTIONS", { diff --git a/modoboa/core/models.py b/modoboa/core/models.py index 3b906618d..390752594 100644 --- a/modoboa/core/models.py +++ b/modoboa/core/models.py @@ -108,7 +108,7 @@ class User(PermissionsMixin): :param raw_value: the new password's value :param curvalue: the current password (for LDAP authentication) """ - if parameters.get_admin("AUTHENTICATION_TYPE") == "local": + if self.is_local: self.password = self._crypt_password(raw_value) else: if not ldap_available: @@ -363,7 +363,7 @@ class User(PermissionsMixin): reversion.register(User) -def populate_callback(user): +def populate_callback(user, group='SimpleUsers'): """Populate callback If the LDAP authentication backend is in use, this callback will @@ -376,7 +376,7 @@ def populate_callback(user): from modoboa.lib.permissions import grant_access_to_object sadmins = User.objects.filter(is_superuser=True) - user.set_role("SimpleUsers") + user.set_role(group) user.post_create(sadmins[0]) for su in sadmins[1:]: grant_access_to_object(su, user) diff --git a/modoboa/core/password_hashers/advanced.py b/modoboa/core/password_hashers/advanced.py index aa844c9ea..3bb632ad2 100644 --- a/modoboa/core/password_hashers/advanced.py +++ b/modoboa/core/password_hashers/advanced.py @@ -16,7 +16,10 @@ class MD5CRYPTHasher(PasswordHasher): """ @property def scheme(self): - return '{MD5-CRYPT}' + return '{MD5-CRYPT}' if self._target == 'local' else '{CRYPT}' + + def _b64encode(self, pwhash): + return pwhash def _encrypt(self, clearvalue, salt=None): return md5_crypt.encrypt(clearvalue) @@ -34,7 +37,10 @@ class SHA256CRYPTHasher(PasswordHasher): """ @property def scheme(self): - return '{SHA256-CRYPT}' + return '{SHA256-CRYPT}' if self._target == 'local' else '{CRYPT}' + + def _b64encode(self, pwhash): + return pwhash def _encrypt(self, clearvalue, salt=None): rounds = int(parameters.get_admin('ROUNDS_NUMBER')) @@ -53,7 +59,10 @@ class SHA512CRYPTHasher(PasswordHasher): """ @property def scheme(self): - return '{SHA512-CRYPT}' + return '{SHA512-CRYPT}' if self._target == 'local' else '{CRYPT}' + + def _b64encode(self, pwhash): + return pwhash def _encrypt(self, clearvalue, salt=None): rounds = int(parameters.get_admin('ROUNDS_NUMBER')) diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py index eb7ddd800..f264fd2d2 100644 --- a/modoboa/core/password_hashers/base.py +++ b/modoboa/core/password_hashers/base.py @@ -15,6 +15,22 @@ class PasswordHasher(object): """ Base class of all hashers. """ + def __init__(self, target='local'): + self._target = target + + def _encrypt(self, clearvalue, salt=None): + raise NotImplementedError + + def _b64encode(self, pwhash): + """Encode :keyword:`pwhash` using base64 if needed. + + :param str pwhash: password hash + :return: base64 encoded hash or original hash + """ + if self._target == 'ldap': + return base64.b64encode(pwhash) + return pwhash + def encrypt(self, clearvalue): """Encrypt a password. @@ -28,7 +44,8 @@ class PasswordHasher(object): :rtype: str :return: encrypted password """ - return '%s%s' % (self.scheme, self._encrypt(clearvalue)) + pwhash = self._b64encode(self._encrypt(clearvalue)) + return '%s%s' % (self.scheme, pwhash) def verify(self, clearvalue, hashed_value): """Verify a password against a hashed value. @@ -97,4 +114,12 @@ class SHA256Hasher(PasswordHasher): return '{SHA256}' def _encrypt(self, clearvalue, salt=None): - return base64.b64encode(hashlib.sha256(clearvalue).digest()) + return hashlib.sha256(clearvalue).digest() + + def _b64encode(self, pwhash): + """Encode :keyword:`pwhash` using base64. + + :param str pwhash: password hash + :return: base64 encoded hash + """ + return base64.b64encode(pwhash) diff --git a/modoboa/extensions/admin/__init__.py b/modoboa/extensions/admin/__init__.py index 7cc61f157..38ca5504c 100644 --- a/modoboa/extensions/admin/__init__.py +++ b/modoboa/extensions/admin/__init__.py @@ -184,8 +184,10 @@ def account_auto_created(user): from modoboa.core.models import User from modoboa.lib.permissions import grant_access_to_object - sadmins = User.objects.filter(is_superuser=True) localpart, domname = split_mailbox(user.username) + if user.group != 'SimpleUsers' and domname is None: + return + sadmins = User.objects.filter(is_superuser=True) try: domain = Domain.objects.get(name=domname) except Domain.DoesNotExist: diff --git a/modoboa/extensions/admin/forms/account.py b/modoboa/extensions/admin/forms/account.py index 2290196de..187a96894 100644 --- a/modoboa/extensions/admin/forms/account.py +++ b/modoboa/extensions/admin/forms/account.py @@ -65,7 +65,7 @@ class AccountFormGeneral(forms.ModelForm): account = kwargs["instance"] self.fields["role"].initial = account.group if not account.is_local \ - and parameters.get_admin("LDAP_AUTH_METHOD") == "directbind": + and parameters.get_admin("LDAP_AUTH_METHOD", app="core") == "directbind": del self.fields["password1"] del self.fields["password2"] diff --git a/modoboa/lib/authbackends.py b/modoboa/lib/authbackends.py index 3e9663604..c08555a4e 100644 --- a/modoboa/lib/authbackends.py +++ b/modoboa/lib/authbackends.py @@ -36,15 +36,23 @@ try: the user. ldap_user.dn is the user's DN and ldap_user.attrs contains all of their LDAP attributes. """ - lpart, domain = split_mailbox(username) - if domain is None: - return None + group = 'SimpleUsers' + admin_groups = parameters \ + .get_admin('LDAP_ADMIN_GROUPS', app='core').split(';') + for grp in admin_groups: + if grp.strip() in ldap_user.group_names: + group = 'DomainAdmins' + break + if group == 'SimpleUsers': + lpart, domain = split_mailbox(username) + if domain is None: + return None user, created = User.objects.get_or_create( username__iexact=username, defaults={'username': username.lower(), 'is_local': False} ) if created: - populate_callback(user) + populate_callback(user, group) return user, created def get_user(self, user_id): diff --git a/modoboa/lib/ldaputils.py b/modoboa/lib/ldaputils.py index ea4723af1..b83e0971c 100644 --- a/modoboa/lib/ldaputils.py +++ b/modoboa/lib/ldaputils.py @@ -34,13 +34,9 @@ Extracted from `this blog """ import ldap -import base64 -import hashlib -import string -import crypt -from random import Random from django.conf import settings from django.utils.translation import ugettext as _ +from modoboa.core.password_hashers import get_password_hasher from modoboa.lib import parameters from modoboa.lib.exceptions import InternalError @@ -71,7 +67,7 @@ class LDAPAuthBackend(object): return conn def connect_to_server(self, user, password): - if parameters.get_admin("LDAP_AUTH_METHOD", app="admin") == "searchbind": + if parameters.get_admin("LDAP_AUTH_METHOD", app="core") == "searchbind": bind_dn = self._setting("AUTH_LDAP_BIND_DN", "") bind_pwd = self._setting("AUTH_LDAP_BIND_PASSWORD", "") self.conn = self._get_conn(bind_dn, bind_pwd) @@ -82,8 +78,8 @@ class LDAPAuthBackend(object): self.conn = self._get_conn(self.user_dn, password) def _find_user_dn(self, user): - sbase = parameters.get_admin("LDAP_SEARCH_BASE", app="admin") - sfilter = parameters.get_admin("LDAP_SEARCH_FILTER", app="admin") + sbase = parameters.get_admin("LDAP_SEARCH_BASE", app="core") + sfilter = parameters.get_admin("LDAP_SEARCH_FILTER", app="core") sfilter = sfilter % {"user": user} res = self.conn.search_s(sbase, ldap.SCOPE_SUBTREE, sfilter) try: @@ -95,25 +91,12 @@ class LDAPAuthBackend(object): def _crypt_password(self, clearpassword): """Overidding of the crypt_password function (LDAP compliant) - The crypted password in base64 encoded and we prepend the used - algorithm to the returned value. (between {}) - :param clearpassword: the clear password :return: the encrypted password """ - scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin") - if scheme == "crypt": - salt = ''.join(Random().sample(string.letters + string.digits, 2)) - result = crypt.crypt(clearpassword, salt) - elif scheme == "md5": - obj = hashlib.md5(clearpassword) - result = obj.digest() - elif scheme == "sha256": - obj = hashlib.sha256(clearpassword) - result = obj.digest() - else: - return str(clearpassword) - return str("{%s}%s" % (scheme.upper(), base64.b64encode(result))) + scheme = parameters.get_admin("PASSWORD_SCHEME", app="core") + hasher = get_password_hasher(scheme.upper())('ldap') + return hasher.encrypt(clearpassword) def update_user_password(self, user, password, newpassword): try: From c6cbdbf242739cc3d12465dcfddc945a8922e4d8 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 26 Nov 2013 15:41:43 +0100 Subject: [PATCH 4/6] Fixing help text. see #449 --- modoboa/core/app_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index 66175df6a..6ed5b9178 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -154,8 +154,8 @@ class GeneralParametersForm(parameters.AdminParametersForm): label=ugettext_lazy("Administrator groups"), initial="", help_text=ugettext_lazy( - "Members of those LDAP groups will be created as domain " - "administrators. Use a ';' characters to separate groups" + "Members of those LDAP Posix groups will be created as domain " + "administrators. Use ';' characters to separate groups." ), required=False ) From 484caa76e8680086ff668880aceca9577bb4bf19 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 26 Nov 2013 16:07:04 +0100 Subject: [PATCH 5/6] Fixing password verification for sha256. --- modoboa/core/password_hashers/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py index f264fd2d2..156a35616 100644 --- a/modoboa/core/password_hashers/base.py +++ b/modoboa/core/password_hashers/base.py @@ -55,7 +55,7 @@ class PasswordHasher(object): :return: True if passwords match, False otherwise """ return constant_time_compare( - self._encrypt(clearvalue, hashed_value), + self._b64encode(self._encrypt(clearvalue, hashed_value)), hashed_value ) From 7859127ea96e85f409a1ee57e171b4adab5c00fa Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 28 Nov 2013 10:52:20 +0100 Subject: [PATCH 6/6] Allowing default domain quota. fixes #500 --- modoboa/extensions/admin/app_settings.py | 20 +++++++++++++++++++- modoboa/extensions/admin/forms/account.py | 16 ++++++++++++++++ modoboa/extensions/admin/forms/domain.py | 21 +++++++++++++++++---- modoboa/extensions/admin/models/domain.py | 4 +--- modoboa/extensions/admin/tests/account.py | 3 ++- modoboa/extensions/admin/tests/domain.py | 17 +++++++++++++++++ 6 files changed, 72 insertions(+), 9 deletions(-) diff --git a/modoboa/extensions/admin/app_settings.py b/modoboa/extensions/admin/app_settings.py index 0ab9f3106..27b01a351 100644 --- a/modoboa/extensions/admin/app_settings.py +++ b/modoboa/extensions/admin/app_settings.py @@ -22,10 +22,20 @@ class AdminParametersForm(parameters.AdminParametersForm): help_text=ugettext_lazy("The UNIX account who owns mailboxes on the filesystem") ) + default_domain_quota = forms.IntegerField( + label=ugettext_lazy("Default domain quota"), + initial=0, + help_text=ugettext_lazy( + "Default quota (in MB) applied to freshly created domains with no " + "value specified. A value of 0 means no quota." + ), + widget=forms.TextInput(attrs={'class': 'span2'}) + ) + auto_account_removal = YesNoField( label=ugettext_lazy("Automatic account removal"), initial="no", - help_text=ugettext_lazy("When a mailbox is removed, also remove the associated account"), + help_text=ugettext_lazy("When a mailbox is removed, also remove the associated account") ) # Visibility rules @@ -46,3 +56,11 @@ class AdminParametersForm(parameters.AdminParametersForm): if hide_fields: del self.fields["handle_mailboxes"] del self.fields["mailboxes_owner"] + + def clean_default_domain_quota(self): + """Ensure quota is a positive integer.""" + if self.cleaned_data['default_domain_quota'] < 0: + raise forms.ValidationError( + ugettext_lazy('Must be a positive integer') + ) + return self.cleaned_data['default_domain_quota'] diff --git a/modoboa/extensions/admin/forms/account.py b/modoboa/extensions/admin/forms/account.py index 187a96894..ca9470f61 100644 --- a/modoboa/extensions/admin/forms/account.py +++ b/modoboa/extensions/admin/forms/account.py @@ -162,6 +162,22 @@ class AccountFormMail(forms.Form, DynamicForm): """Ensure lower case emails""" return self.cleaned_data["email"].lower() + def clean(self): + """Custom fields validation. + + Check if quota is >= 0 only when the domain value is not used. + """ + super(AccountFormMail, self).clean() + if self._errors: + raise forms.ValidationError(self._errors) + if not self.cleaned_data["quota_act"] \ + and self.cleaned_data['quota'] is not None: + if self.cleaned_data["quota"] < 0: + self._errors["quota"] = self.error_class( + [_("Must be a valid integer")]) + del self.cleaned_data["quota"] + return self.cleaned_data + def create_mailbox(self, user, account): locpart, domname = split_mailbox(self.cleaned_data["email"]) try: diff --git a/modoboa/extensions/admin/forms/domain.py b/modoboa/extensions/admin/forms/domain.py index c19b60d68..9bdfe81c5 100644 --- a/modoboa/extensions/admin/forms/domain.py +++ b/modoboa/extensions/admin/forms/domain.py @@ -1,7 +1,7 @@ from django import forms from django.http import QueryDict from django.utils.translation import ugettext as _, ugettext_lazy -from modoboa.lib import events +from modoboa.lib import events, parameters from modoboa.lib.exceptions import Conflict from modoboa.lib.formutils import ( DomainNameField, YesNoField, DynamicForm, TabForms @@ -13,6 +13,15 @@ from modoboa.extensions.admin.models import ( class DomainFormGeneral(forms.ModelForm, DynamicForm): + quota = forms.IntegerField( + label=ugettext_lazy("Quota"), + required=False, + help_text=ugettext_lazy( + "Default quota in MB applied to mailboxes. Leave empty to use the " + "default value." + ), + widget=forms.TextInput(attrs={"class": "span1"}) + ) aliases = DomainNameField( label=ugettext_lazy("Alias(es)"), required=False, @@ -25,9 +34,6 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm): class Meta: model = Domain fields = ("name", "quota", "aliases", "enabled") - widgets = dict( - quota=forms.widgets.TextInput(attrs={"class": "span1"}) - ) def __init__(self, *args, **kwargs): self.oldname = None @@ -43,6 +49,13 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm): name = "aliases_%d" % (pos + 1) self._create_field(forms.CharField, name, dalias.name, 3) + def clean_quota(self): + if self.cleaned_data['quota'] is None: + return int(parameters.get_admin('DEFAULT_DOMAIN_QUOTA')) + if self.cleaned_data['quota'] < 0: + raise forms.ValidationError(_("Must be a positive integer")) + return self.cleaned_data['quota'] + def clean(self): """Custom fields validation. diff --git a/modoboa/extensions/admin/models/domain.py b/modoboa/extensions/admin/models/domain.py index e7467d592..7c19710dd 100644 --- a/modoboa/extensions/admin/models/domain.py +++ b/modoboa/extensions/admin/models/domain.py @@ -27,9 +27,7 @@ class Domain(AdminObject): """ name = models.CharField(ugettext_lazy('name'), max_length=100, unique=True, help_text=ugettext_lazy("The domain name")) - quota = models.IntegerField( - help_text=ugettext_lazy("Default quota in MB applied to mailboxes") - ) + quota = models.IntegerField() enabled = models.BooleanField( ugettext_lazy('enabled'), help_text=ugettext_lazy("Check to activate this domain") diff --git a/modoboa/extensions/admin/tests/account.py b/modoboa/extensions/admin/tests/account.py index 547e170ab..8a79674bf 100644 --- a/modoboa/extensions/admin/tests/account.py +++ b/modoboa/extensions/admin/tests/account.py @@ -85,7 +85,8 @@ class PermissionsTestCase(ModoTestCase): self.user = User.objects.get(username='user@test.com') self.values = dict( username=self.user.username, role="DomainAdmins", - is_active=self.user.is_active, email="user@test.com" + is_active=self.user.is_active, email="user@test.com", + quota_act=True ) def tearDown(self): diff --git a/modoboa/extensions/admin/tests/domain.py b/modoboa/extensions/admin/tests/domain.py index 929afd3c8..9e3fba111 100644 --- a/modoboa/extensions/admin/tests/domain.py +++ b/modoboa/extensions/admin/tests/domain.py @@ -1,5 +1,6 @@ from django.core.urlresolvers import reverse from modoboa.core.models import User +from modoboa.lib import parameters from modoboa.lib.tests import ModoTestCase from modoboa.extensions.admin.models import ( Domain, Alias @@ -66,6 +67,22 @@ class DomainTestCase(ModoTestCase): self.assertIn(da.mailbox_set.all()[0], al.mboxes.all()) self.assertTrue(da.can_access(al)) + def test_create_using_default_quota(self): + parameters.save_admin('DEFAULT_DOMAIN_QUOTA', 50, app='admin') + values = { + "name": "pouet.com", "create_dom_admin": "yes", + "dom_admin_username": "toto", "create_aliases": "yes", + "stepid": 'step2' + } + self.ajax_post( + reverse("modoboa.extensions.admin.views.domain.newdomain"), + values + ) + dom = Domain.objects.get(name="pouet.com") + self.assertEqual(dom.quota, 50) + da = User.objects.get(username="toto@pouet.com") + self.assertEqual(da.mailbox_set.all()[0].quota, 50) + def test_modify(self): """Test the modification of a domain