From 3fb1c13f18f438d7cb0191db5690704d6d69bcfd Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Wed, 26 Oct 2016 17:11:28 +0200 Subject: [PATCH] WIP see #951 --- doc/contributing/events.rst | 14 - modoboa/admin/app_settings.py | 12 +- modoboa/admin/callbacks.py | 5 +- modoboa/admin/forms/account.py | 9 +- modoboa/admin/forms/domain.py | 14 +- .../commands/handle_mailbox_operations.py | 30 +- .../management/commands/subcommands/_mx.py | 10 +- modoboa/admin/models/domain.py | 17 +- modoboa/admin/models/mailbox.py | 24 +- modoboa/admin/views/identity.py | 2 +- modoboa/core/app_settings.py | 10 +- modoboa/core/apps.py | 10 +- .../core/commands/templates/settings.py.tpl | 2 + modoboa/core/forms.py | 11 +- modoboa/core/management/commands/cleanlogs.py | 5 +- .../management/commands/load_initial_data.py | 1 - modoboa/core/models.py | 17 +- modoboa/core/password_hashers/advanced.py | 13 +- modoboa/core/templates/core/parameters.html | 2 +- .../core/templates/core/user_preferences.html | 2 +- modoboa/core/views/admin.py | 10 +- modoboa/core/views/user.py | 12 +- modoboa/lib/authbackends.py | 13 +- modoboa/lib/cryptutils.py | 7 +- modoboa/lib/ldap_utils.py | 18 +- modoboa/lib/listing.py | 5 +- modoboa/lib/parameters.py | 325 ------------------ modoboa/lib/tests.py | 75 ---- modoboa/limits/app_settings.py | 8 +- modoboa/limits/apps.py | 10 +- modoboa/limits/controls.py | 12 +- modoboa/limits/general_callbacks.py | 15 +- modoboa/limits/handlers.py | 21 +- modoboa/parameters/__init__.py | 0 modoboa/parameters/apps.py | 7 + modoboa/parameters/forms.py | 107 ++++++ .../templates/parameters/base_form.html} | 0 modoboa/parameters/tests.py | 3 + .../parameters.py => parameters/tools.py} | 56 ++- modoboa/relaydomains/app_settings.py | 4 +- modoboa/relaydomains/apps.py | 8 +- modoboa/relaydomains/models.py | 4 +- 42 files changed, 352 insertions(+), 578 deletions(-) delete mode 100644 modoboa/lib/parameters.py create mode 100644 modoboa/parameters/__init__.py create mode 100644 modoboa/parameters/apps.py create mode 100644 modoboa/parameters/forms.py rename modoboa/{templates/common/parameters.html => parameters/templates/parameters/base_form.html} (100%) create mode 100644 modoboa/parameters/tests.py rename modoboa/{core/parameters.py => parameters/tools.py} (74%) diff --git a/doc/contributing/events.rst b/doc/contributing/events.rst index 6e7e48889..26d5eb707 100644 --- a/doc/contributing/events.rst +++ b/doc/contributing/events.rst @@ -563,20 +563,6 @@ Must return a list of set. Each set must contain at least three entries:: [('', '', '')] -GetExtraParameters -================== - -Raised to request extra parameters for a given parameters form. - -*Callback prototype*:: - - def callback(application, level): pass - -* ``application`` is the name of the form's application (ie. admin, amavis, etc.) -* ``level`` is the form's level: ``A`` for admin or ``U`` for user - -Must return a dictionary. Each entry must be a valid Django form field. - .. _getextrarolepermissions: GetExtraRolePermissions diff --git a/modoboa/admin/app_settings.py b/modoboa/admin/app_settings.py index 8fc341664..f695ee673 100644 --- a/modoboa/admin/app_settings.py +++ b/modoboa/admin/app_settings.py @@ -6,11 +6,11 @@ from django import forms from django.conf import settings from django.utils.translation import ugettext_lazy -from ..lib import events -from modoboa.lib import parameters from modoboa.lib.form_utils import YesNoField, SeparatorField from modoboa.lib.sysutils import exec_cmd +from modoboa.parameters import forms as param_forms +from ..lib import events ADMIN_EVENTS = [ "DomainCreated", @@ -48,7 +48,7 @@ ADMIN_EVENTS = [ ] -class AdminParametersForm(parameters.AdminParametersForm): +class AdminParametersForm(param_forms.AdminParametersForm): app = "admin" dom_sep = SeparatorField(label=ugettext_lazy("Domains")) @@ -174,9 +174,9 @@ class AdminParametersForm(parameters.AdminParametersForm): def load_admin_settings(): """Load admin settings.""" - from .app_settings import AdminParametersForm + from modoboa.parameters import tools as param_tools - parameters.register( - AdminParametersForm, ugettext_lazy("Administration")) + param_tools.registry.add( + "global", AdminParametersForm, ugettext_lazy("Administration")) events.declare(ADMIN_EVENTS) from . import callbacks diff --git a/modoboa/admin/callbacks.py b/modoboa/admin/callbacks.py index 41de9e794..59e59313a 100644 --- a/modoboa/admin/callbacks.py +++ b/modoboa/admin/callbacks.py @@ -4,9 +4,10 @@ from django.core.urlresolvers import reverse from django.utils.translation import ugettext as _, ugettext_lazy from modoboa.core import signals as core_signals -from modoboa.lib import parameters, events +from modoboa.lib import events from modoboa.lib.email_utils import split_mailbox from modoboa.lib.exceptions import PermDeniedException, BadRequest, Conflict +from modoboa.parameters import tools as param_tools from .models import ( Domain, DomainAlias, Mailbox, Alias @@ -154,7 +155,7 @@ def account_auto_created(user): from modoboa.lib.permissions import grant_access_to_object from .lib import check_if_domain_exists - if not parameters.get_admin("AUTO_CREATE_DOMAIN_AND_MAILBOX"): + if not param_tools.get_global_parameter("auto_create_domain_and_mailbox"): return localpart, domname = split_mailbox(user.username) if user.role != 'SimpleUsers' and domname is None: diff --git a/modoboa/admin/forms/account.py b/modoboa/admin/forms/account.py index 36246ba38..920b0cd10 100644 --- a/modoboa/admin/forms/account.py +++ b/modoboa/admin/forms/account.py @@ -9,9 +9,9 @@ from django.utils.translation import ugettext as _, ugettext_lazy from passwords.fields import PasswordField -from modoboa.core.models import User from modoboa.core import signals as core_signals -from modoboa.lib import events, parameters +from modoboa.core.models import User +from modoboa.lib import events from modoboa.lib.email_utils import split_mailbox from modoboa.lib import exceptions as lib_exceptions from modoboa.lib import fields as lib_fields @@ -21,6 +21,7 @@ from modoboa.lib.form_utils import ( from modoboa.lib.permissions import get_account_roles from modoboa.lib.validators import validate_utf8_email from modoboa.lib.web_utils import render_to_json_response +from modoboa.parameters import tools as param_tools from .. import models @@ -111,8 +112,8 @@ class AccountFormGeneral(forms.ModelForm): self.fields["role"].initial = self.instance.role condition = ( not self.instance.is_local and - parameters.get_admin( - "LDAP_AUTH_METHOD", app="core") == "directbind") + param_tools.get_global_parameter( + "ldap_auth_method", app="core") == "directbind") if condition: del self.fields["password1"] del self.fields["password2"] diff --git a/modoboa/admin/forms/domain.py b/modoboa/admin/forms/domain.py index 0c439acd7..a1c649526 100644 --- a/modoboa/admin/forms/domain.py +++ b/modoboa/admin/forms/domain.py @@ -5,15 +5,16 @@ from django.core.urlresolvers import reverse from django.http import QueryDict from django.utils.translation import ugettext as _, ugettext_lazy -from modoboa.core.models import User from modoboa.core import signals as core_signals -from modoboa.lib import events, parameters +from modoboa.core.models import User +from modoboa.lib import events from modoboa.lib.exceptions import Conflict from modoboa.lib.fields import DomainNameField from modoboa.lib.form_utils import ( YesNoField, WizardForm, WizardStep, DynamicForm, TabForms ) from modoboa.lib.web_utils import render_to_json_response +from modoboa.parameters import tools as param_tools from ..lib import check_if_domain_exists from ..models import ( @@ -75,11 +76,12 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm): 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: + """Check quota value.""" + if self.cleaned_data["quota"] is None: + return param_tools.get_global_parameter("default_domain_quota") + if self.cleaned_data["quota"] < 0: raise forms.ValidationError(_("Must be a positive integer")) - return self.cleaned_data['quota'] + return self.cleaned_data["quota"] def clean(self): """Custom fields validation. diff --git a/modoboa/admin/management/commands/handle_mailbox_operations.py b/modoboa/admin/management/commands/handle_mailbox_operations.py index b33be3a8d..c9bc1230d 100644 --- a/modoboa/admin/management/commands/handle_mailbox_operations.py +++ b/modoboa/admin/management/commands/handle_mailbox_operations.py @@ -1,3 +1,4 @@ +"""A management command to apply mailbox operations.""" import logging from optparse import make_option @@ -5,7 +6,7 @@ import os from django.core.management.base import BaseCommand -from modoboa.lib import parameters +from param_tools import tools as param_tools from modoboa.lib.sysutils import exec_cmd from modoboa.lib.exceptions import InternalError @@ -14,22 +15,26 @@ from ...models import MailboxOperation class OperationError(Exception): + """Custom exception.""" + pass class Command(BaseCommand): - help = 'Handles rename and delete operations on mailboxes' + """Command definition""" + + help = "Handles rename and delete operations on mailboxes" option_list = BaseCommand.option_list + ( make_option( - '--pidfile', default='/tmp/handle_mailbox_operations.pid', - help='Path to the file that will contain the PID of this process' + "--pidfile", default="/tmp/handle_mailbox_operations.pid", + help="Path to the file that will contain the PID of this process" ), ) def __init__(self, *args, **kwargs): super(Command, self).__init__(*args, **kwargs) - self.logger = logging.getLogger('modoboa.admin') + self.logger = logging.getLogger("modoboa.admin") def rename_mailbox(self, operation): if not os.path.exists(operation.argument): @@ -75,22 +80,23 @@ class Command(BaseCommand): return True def handle(self, *args, **options): + """Command entry point.""" load_admin_settings() - if not parameters.get_admin("HANDLE_MAILBOXES"): + if not param_tools.get_global_parameter("handle_mailboxes"): return - if not self.check_pidfile(options['pidfile']): + if not self.check_pidfile(options["pidfile"]): return for ope in MailboxOperation.objects.all(): try: - f = getattr(self, '%s_mailbox' % ope.type) + f = getattr(self, "%s_mailbox" % ope.type) except AttributeError: continue try: f(ope) except (OperationError, InternalError) as e: - self.logger.critical('%s failed (reason: %s)', - ope, str(e).encode('utf-8')) + self.logger.critical("%s failed (reason: %s)", + ope, str(e).encode("utf-8")) else: - self.logger.info('%s succeed', ope) + self.logger.info("%s succeed", ope) ope.delete() - os.unlink(options['pidfile']) + os.unlink(options["pidfile"]) diff --git a/modoboa/admin/management/commands/subcommands/_mx.py b/modoboa/admin/management/commands/subcommands/_mx.py index e3bbb20a0..13c8bfb77 100644 --- a/modoboa/admin/management/commands/subcommands/_mx.py +++ b/modoboa/admin/management/commands/subcommands/_mx.py @@ -17,8 +17,8 @@ from django.utils.translation import ugettext as _ from modoboa.admin import constants from modoboa.admin import models -from modoboa.lib import parameters from modoboa.lib import email_utils +from modoboa.parameters import tools as param_tools class CheckMXRecords(BaseCommand): @@ -36,12 +36,12 @@ class CheckMXRecords(BaseCommand): @cached_property def sender(self): """Return sender address for notifications.""" - return parameters.get_admin("SENDER_ADDRESS", app="core") + return param_tools.get_global_parameter("sender_address", app="core") @cached_property def valid_mxs(self): """Return valid MXs set in admin.""" - valid_mxs = parameters.get_admin("VALID_MXS", app="admin") + valid_mxs = param_tools.get_global_parameter("valid_mxs") return [ipaddress.ip_network(u"{}".format(v.strip())) for v in valid_mxs.split() if v.strip()] @@ -217,11 +217,11 @@ class CheckMXRecords(BaseCommand): """Check specified domain.""" mx_list = list(self.get_mx_records_for_domain(domain, ttl=ttl)) - if parameters.get_admin("ENABLE_MX_CHECKS"): + if param_tools.get_global_parameter("enable_mx_checks"): self.check_valid_mx(domain, mx_list, **options) condition = ( - not parameters.get_admin("ENABLE_DNSBL_CHECKS") or + not param_tools.get_global_parameter("enable_dnsbl_checks") or options["no_dnsbl"] is True) if condition or not mx_list: return diff --git a/modoboa/admin/models/domain.py b/modoboa/admin/models/domain.py index d52adb38b..476764e00 100644 --- a/modoboa/admin/models/domain.py +++ b/modoboa/admin/models/domain.py @@ -13,10 +13,11 @@ from django.contrib.contenttypes.fields import GenericRelation from reversion import revisions as reversion -from modoboa.core.models import User, ObjectAccess from modoboa.core import signals as core_signals -from modoboa.lib import events, parameters +from modoboa.core.models import User, ObjectAccess +from modoboa.lib import events from modoboa.lib.exceptions import BadRequest, Conflict +from modoboa.parameters import tools as param_tools from .base import AdminObject @@ -185,11 +186,11 @@ class Domain(AdminObject): if self.alias_set.count(): events.raiseEvent("MailboxAliasDeleted", self.alias_set.all()) ungrant_access_to_objects(self.alias_set.all()) - if parameters.get_admin("AUTO_ACCOUNT_REMOVAL"): + if param_tools.get_global_parameter("auto_account_removal"): for account in User.objects.filter(mailbox__domain=self): account.delete(fromuser, keepdir) elif self.mailbox_set.count(): - Quota.objects.filter(username__contains='@%s' % self.name).delete() + 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() @@ -242,7 +243,7 @@ class MXQuerySet(models.QuerySet): def has_valids(self): """Return managed results.""" - if parameters.get_admin("VALID_MXS", app="admin").strip(): + if param_tools.get_global_parameter("valid_mxs").strip(): return self.filter(managed=True).exists() return self.exists() @@ -259,12 +260,12 @@ class MXRecord(models.Model): objects = models.Manager.from_queryset(MXQuerySet)() def is_managed(self): - if not parameters.get_admin("ENABLE_MX_CHECKS"): + if not param_tools.get_global_parameter("enable_mx_checks"): return False - return bool(parameters.get_admin("VALID_MXS", app="admin").strip()) + return bool(param_tools.get_global_parameter("valid_mxs").strip()) def __unicode__(self): - return u'{0.name} ({0.address}) for {0.domain} '.format(self) + return u"{0.name} ({0.address}) for {0.domain} ".format(self) class DNSBLQuerySet(models.QuerySet): diff --git a/modoboa/admin/models/mailbox.py b/modoboa/admin/models/mailbox.py index 3db2ae118..f4065d6c4 100644 --- a/modoboa/admin/models/mailbox.py +++ b/modoboa/admin/models/mailbox.py @@ -12,13 +12,15 @@ from django.utils.translation import ugettext as _, ugettext_lazy from reversion import revisions as reversion -from .base import AdminObject -from .domain import Domain from modoboa.core.models import User -from modoboa.lib import events, parameters +from modoboa.lib import events from modoboa.lib import exceptions as lib_exceptions from modoboa.lib.email_utils import split_mailbox from modoboa.lib.sysutils import exec_cmd +from modoboa.parameters import tools as param_tools + +from .base import AdminObject +from .domain import Domain class Quota(models.Model): @@ -131,15 +133,15 @@ class Mailbox(AdminObject): several patterns to understand and we don't want to implement them. """ - hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False) - if not hm: + admin_params = dict(param_tools.get_global_parameters("admin")) + if not admin_params.get("handle_mailboxes"): return None if self.__mail_home is None: curuser = pwd.getpwuid(os.getuid()).pw_name - mbowner = parameters.get_admin("MAILBOXES_OWNER") + mbowner = admin_params["mailboxes_owner"] options = {} if curuser != mbowner: - options['sudo_user'] = mbowner + options["sudo_user"] = mbowner code, output = exec_cmd( "doveadm user %s -f home" % self.full_address, **options ) @@ -180,7 +182,8 @@ class Mailbox(AdminObject): def rename_dir(self, old_mail_home): """Rename local directory if needed.""" - hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False) + hm = param_tools.get_global_parameter( + "handle_mailboxes", raise_exception=False) if not hm: return MailboxOperation.objects.create( @@ -210,10 +213,11 @@ class Mailbox(AdminObject): self.rename_dir(old_mail_home) def delete_dir(self): - hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False) + hm = param_tools.get_global_parameter( + "handle_mailboxes", raise_exception=False) if not hm: return - MailboxOperation.objects.create(type='delete', argument=self.mail_home) + MailboxOperation.objects.create(type="delete", argument=self.mail_home) def set_quota(self, value=None, override_rules=False): """Set or update quota's value for this mailbox. diff --git a/modoboa/admin/views/identity.py b/modoboa/admin/views/identity.py index 1518c6682..466ced291 100644 --- a/modoboa/admin/views/identity.py +++ b/modoboa/admin/views/identity.py @@ -13,7 +13,7 @@ from django.contrib.auth.decorators import ( from reversion import revisions as reversion from modoboa.core.models import User -from modoboa.lib import parameters, events +from modoboa.lib import events from modoboa.lib.exceptions import ( PermDeniedException, BadRequest ) diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index c6633cd81..697fcfb7c 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -3,12 +3,13 @@ from django import forms from django.conf import settings from django.utils.translation import ugettext as _, ugettext_lazy -from modoboa.lib import parameters from modoboa.lib import fields as lib_fields from modoboa.lib.cryptutils import random_key from modoboa.lib.form_utils import ( YesNoField, SeparatorField, InlineRadioSelect ) +from modoboa.parameters import forms as param_forms +from modoboa.parameters import tools as param_tools from . import constants @@ -30,8 +31,7 @@ def enabled_applications(): return sorted(result, key=lambda e: e[0]) -class GeneralParametersForm(parameters.AdminParametersForm): - +class GeneralParametersForm(param_forms.AdminParametersForm): """General parameters.""" app = "core" @@ -348,7 +348,7 @@ class GeneralParametersForm(parameters.AdminParametersForm): return cleaned_data def to_django_settings(self): - """Apply LDAP related parameters to Django settings + """Apply LDAP related parameters to Django settings. Doing so, we can use the django_auth_ldap module. """ @@ -360,7 +360,7 @@ class GeneralParametersForm(parameters.AdminParametersForm): except ImportError: ldap_available = False - values = self.get_current_values() + values = dict(param_tools.get_global_parameters("core")) if not ldap_available or values["authentication_type"] != "ldap": return if not hasattr(settings, "AUTH_LDAP_USER_ATTR_MAP"): diff --git a/modoboa/core/apps.py b/modoboa/core/apps.py index 1b8e3cf17..df124bd32 100644 --- a/modoboa/core/apps.py +++ b/modoboa/core/apps.py @@ -2,7 +2,7 @@ from django.apps import AppConfig from django.db.models import signals - +from django.utils.translation import ugettext_lazy BASE_EVENTS = [ "AccountCreated", @@ -36,7 +36,6 @@ BASE_EVENTS = [ "ExtraUprefsRoutes", "ExtraUprefsJS", - "GetExtraParameters", "ExtraFormFields", "SaveExtraFormFields", ] @@ -48,11 +47,12 @@ def load_core_settings(): This function must be manually called (see :file:`urls.py`) in order to load base settings. """ - from django.utils.translation import ugettext_lazy - from modoboa.lib import parameters, events + from modoboa.lib import events + from modoboa.parameters import tools as param_tools from .app_settings import GeneralParametersForm - parameters.register(GeneralParametersForm, ugettext_lazy("General")) + param_tools.registry.add( + "global", GeneralParametersForm, ugettext_lazy("General")) events.declare(BASE_EVENTS) diff --git a/modoboa/core/commands/templates/settings.py.tpl b/modoboa/core/commands/templates/settings.py.tpl index 5eb1d00e0..1ed22bc62 100644 --- a/modoboa/core/commands/templates/settings.py.tpl +++ b/modoboa/core/commands/templates/settings.py.tpl @@ -63,6 +63,7 @@ MODOBOA_APPS = ( 'modoboa.admin', 'modoboa.relaydomains', 'modoboa.limits', + 'modoboa.parameters', # Modoboa extensions here. {% for extension in extensions %} '{{ extension }}', {% endfor %} @@ -81,6 +82,7 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'modoboa.core.middleware.LocalConfigMiddleware', 'modoboa.lib.middleware.AjaxLoginRedirect', 'modoboa.lib.middleware.CommonExceptionCatcher', 'modoboa.lib.middleware.RequestCatcherMiddleware', diff --git a/modoboa/core/forms.py b/modoboa/core/forms.py index 45bdb5f55..09ccd9ee5 100644 --- a/modoboa/core/forms.py +++ b/modoboa/core/forms.py @@ -8,11 +8,10 @@ from django.utils.translation import ugettext as _, ugettext_lazy from passwords.fields import PasswordField from modoboa.core.models import User -from modoboa.lib import parameters +from modoboa.parameters import tools as param_tools class LoginForm(forms.Form): - """User login form.""" username = forms.CharField( @@ -30,7 +29,6 @@ class LoginForm(forms.Form): class ProfileForm(forms.ModelForm): - """Form to update User profile.""" oldpassword = forms.CharField( @@ -51,8 +49,8 @@ class ProfileForm(forms.ModelForm): fields = ("first_name", "last_name", "language", "phone_number", "secondary_email") widgets = { - 'first_name': forms.TextInput(attrs={'class': 'form-control'}), - 'last_name': forms.TextInput(attrs={'class': 'form-control'}) + "first_name": forms.TextInput(attrs={"class": "form-control"}), + "last_name": forms.TextInput(attrs={"class": "form-control"}) } def __init__(self, update_password, *args, **kwargs): @@ -66,7 +64,7 @@ class ProfileForm(forms.ModelForm): if self.cleaned_data["oldpassword"] == "": return self.cleaned_data["oldpassword"] - if parameters.get_admin("AUTHENTICATION_TYPE") != "local": + if param_tools.get_global_parameter("authentication_type") != "local": return self.cleaned_data["oldpassword"] if not self.instance.check_password(self.cleaned_data["oldpassword"]): @@ -93,7 +91,6 @@ class ProfileForm(forms.ModelForm): class APIAccessForm(forms.Form): - """Form to control API access.""" enable_api_access = forms.BooleanField( diff --git a/modoboa/core/management/commands/cleanlogs.py b/modoboa/core/management/commands/cleanlogs.py index 5cb7147a7..033d51957 100644 --- a/modoboa/core/management/commands/cleanlogs.py +++ b/modoboa/core/management/commands/cleanlogs.py @@ -7,7 +7,8 @@ from django.core.management.base import BaseCommand from django.utils import timezone from modoboa.core.models import Log -from modoboa.lib import parameters + +from ... import parameters class Command(BaseCommand): @@ -38,7 +39,7 @@ class Command(BaseCommand): l.addHandler(logging.StreamHandler()) self.verbose = options["verbose"] - log_maximum_age = int(parameters.get_admin("LOG_MAXIMUM_AGE")) + log_maximum_age = parameters.get_global_parameter("log_maximum_age") self.__vprint("Deleting logs older than %d days..." % log_maximum_age) limit = timezone.now() - datetime.timedelta(log_maximum_age) Log.objects.filter(date_created__lt=limit).delete() diff --git a/modoboa/core/management/commands/load_initial_data.py b/modoboa/core/management/commands/load_initial_data.py index 58ac166d0..09dbcd7bb 100644 --- a/modoboa/core/management/commands/load_initial_data.py +++ b/modoboa/core/management/commands/load_initial_data.py @@ -12,7 +12,6 @@ from modoboa.core import PERMISSIONS from modoboa.core.extensions import exts_pool from modoboa.lib.cryptutils import random_key from modoboa.lib import events -from modoboa.lib import models as lib_models from modoboa.lib.permissions import add_permissions_to_group import modoboa.relaydomains.models as relay_models diff --git a/modoboa/core/models.py b/modoboa/core/models.py index 7eba10e4c..0fbda4dff 100644 --- a/modoboa/core/models.py +++ b/modoboa/core/models.py @@ -21,13 +21,12 @@ from reversion import revisions as reversion from modoboa.core.password_hashers import get_password_hasher from modoboa.lib import events -from modoboa.lib import parameters as lib_parameters from modoboa.lib.exceptions import ( PermDeniedException, InternalError, BadRequest, Conflict ) +from modoboa.parameters import tools as param_tools from . import constants -from . import parameters try: @@ -102,7 +101,7 @@ class User(PermissionsMixin): def __init__(self, *args, **kwargs): """Load parameter manager.""" super(User, self).__init__(*args, **kwargs) - self.parameters = parameters.Manager("user", self._parameters) + self.parameters = param_tools.Manager("user", self._parameters) def delete(self, fromuser, *args, **kwargs): """Custom delete method @@ -147,13 +146,13 @@ class User(PermissionsMixin): one more time. """ - try: - scheme = lib_parameters.get_admin("PASSWORD_SCHEME") - except parameters.NotDefined: + scheme = param_tools.get_global_parameter( + "password_scheme", raise_exception=False) + if scheme is None: from modoboa.core.apps import load_core_settings load_core_settings() - scheme = lib_parameters.get_admin("PASSWORD_SCHEME") - + scheme = param_tools.get_global_parameter( + "password_scheme", raise_exception=False) if isinstance(raw_value, unicode): raw_value = raw_value.encode("utf-8") return get_password_hasher(scheme.upper())().encrypt(raw_value) @@ -520,4 +519,4 @@ class LocalConfig(models.Model): def __init__(self, *args, **kwargs): """Load parameter manager.""" super(LocalConfig, self).__init__(*args, **kwargs) - self.parameters = parameters.Manager("admin", self._parameters) + self.parameters = param_tools.Manager("global", self._parameters) diff --git a/modoboa/core/password_hashers/advanced.py b/modoboa/core/password_hashers/advanced.py index e4c4c54b9..c14245e29 100644 --- a/modoboa/core/password_hashers/advanced.py +++ b/modoboa/core/password_hashers/advanced.py @@ -3,9 +3,12 @@ Advanced (ie. stronger) password hashers. This module relies on `passlib` to provide more secure hashers. """ + from passlib.hash import bcrypt, md5_crypt, sha256_crypt, sha512_crypt -from modoboa.core.password_hashers.base import PasswordHasher -from modoboa.lib import parameters + +from modoboa.parameters import tools as param_tools + +from .base import PasswordHasher class BLFCRYPTHasher(PasswordHasher): @@ -26,7 +29,7 @@ class BLFCRYPTHasher(PasswordHasher): # Using the parameter for rounds will generate the error # ValueError: rounds too high (bcrypt requires <= 31 rounds) # when using the bcrypt hasher. - # rounds = int(parameters.get_admin('ROUNDS_NUMBER')) + # rounds = parameters.get_global_parameter("rounds_number") # To get around this, I use the default of 12. rounds = 12 return bcrypt.encrypt(clearvalue, rounds=rounds) @@ -72,7 +75,7 @@ class SHA256CRYPTHasher(PasswordHasher): return pwhash def _encrypt(self, clearvalue, salt=None): - rounds = int(parameters.get_admin('ROUNDS_NUMBER')) + rounds = param_tools.get_global_parameter("rounds_number") return sha256_crypt.encrypt(clearvalue, rounds=rounds) def verify(self, clearvalue, hashed_value): @@ -95,7 +98,7 @@ class SHA512CRYPTHasher(PasswordHasher): return pwhash def _encrypt(self, clearvalue, salt=None): - rounds = int(parameters.get_admin('ROUNDS_NUMBER')) + rounds = param_tools.get_global_parameter("rounds_number") return sha512_crypt.encrypt(clearvalue, rounds=rounds) def verify(self, clearvalue, hashed_value): diff --git a/modoboa/core/templates/core/parameters.html b/modoboa/core/templates/core/parameters.html index a4b8538e4..a533a9a5b 100644 --- a/modoboa/core/templates/core/parameters.html +++ b/modoboa/core/templates/core/parameters.html @@ -1,7 +1,7 @@ {% load i18n lib_tags %}

{% trans "Parameters" %} {% trans "Configure Modoboa" %}


{% csrf_token %} - {% include "common/parameters.html" %} + {% include "parameters/base_form.html" %}
diff --git a/modoboa/core/templates/core/user_preferences.html b/modoboa/core/templates/core/user_preferences.html index a13dcf1ed..7d599207e 100644 --- a/modoboa/core/templates/core/user_preferences.html +++ b/modoboa/core/templates/core/user_preferences.html @@ -5,7 +5,7 @@
{% csrf_token %} - {% include "common/parameters.html" %} + {% include "parameters/base_form.html" %}
diff --git a/modoboa/core/views/admin.py b/modoboa/core/views/admin.py index 8f4161020..288c4465b 100644 --- a/modoboa/core/views/admin.py +++ b/modoboa/core/views/admin.py @@ -9,13 +9,13 @@ from django.template.loader import render_to_string from django.utils.translation import ugettext as _ from modoboa.core.models import Log -from modoboa.core import parameters as core_parameters from modoboa.core.utils import check_for_updates from modoboa.lib import events from modoboa.lib.listing import get_sort_order, get_listing_page from modoboa.lib.web_utils import ( _render_to_string, render_to_json_response ) +from modoboa.parameters import tools as param_tools @login_required @@ -32,8 +32,8 @@ def viewparameters(request, tplname='core/parameters.html'): return render_to_json_response({ "left_selection": "parameters", "content": _render_to_string(request, tplname, { - "forms": core_parameters.registry.get_forms( - "admin", localconfig=request.localconfig) + "forms": param_tools.registry.get_forms( + "global", localconfig=request.localconfig) }) }) @@ -42,8 +42,8 @@ def viewparameters(request, tplname='core/parameters.html'): @user_passes_test(lambda u: u.is_superuser) def saveparameters(request): """Save all parameters.""" - forms = core_parameters.registry.get_forms( - "admin", request.POST, localconfig=request.localconfig) + forms = param_tools.registry.get_forms( + "global", request.POST, localconfig=request.localconfig) for formdef in forms: form = formdef["form"] if form.is_valid(): diff --git a/modoboa/core/views/user.py b/modoboa/core/views/user.py index ea4e1e11a..d040cf8d2 100644 --- a/modoboa/core/views/user.py +++ b/modoboa/core/views/user.py @@ -8,12 +8,13 @@ from django.contrib.auth.decorators import login_required, user_passes_test from rest_framework.authtoken.models import Token -from modoboa.core import parameters as core_parameters -from modoboa.lib import events, parameters +from modoboa.lib import events from modoboa.lib.cryptutils import encrypt from modoboa.lib.web_utils import ( _render_to_string, render_to_json_response ) +from modoboa.parameters import tools as param_tools + from ..forms import ProfileForm, APIAccessForm @@ -60,7 +61,7 @@ def profile(request, tplname='core/user_profile.html'): @login_required def preferences(request): if request.method == "POST": - forms = core_parameters.registry.get_forms( + forms = param_tools.registry.get_forms( "user", request.POST, user=request.user) for formdef in forms: form = formdef["form"] @@ -70,12 +71,13 @@ def preferences(request): return render_to_json_response({ "prefix": form.app, "form_errors": form.errors }, status=400) - + request.user.save() return render_to_json_response(_("Preferences saved")) return render_to_json_response({ "content": _render_to_string(request, "core/user_preferences.html", { - "forms": parameters.get_user_forms(request.user) + "forms": param_tools.registry.get_forms( + "user", user=request.user, first_app="general") }) }) diff --git a/modoboa/lib/authbackends.py b/modoboa/lib/authbackends.py index 956a4f314..74653c67c 100644 --- a/modoboa/lib/authbackends.py +++ b/modoboa/lib/authbackends.py @@ -3,12 +3,11 @@ from django.contrib.auth.backends import ModelBackend from modoboa.core.models import User -from modoboa.lib import parameters from modoboa.lib.email_utils import split_mailbox +from modoboa.parameters import tools as param_tools class SimpleBackend(ModelBackend): - """Simple authentication backend.""" def authenticate(self, username=None, password=None): @@ -36,7 +35,7 @@ try: def __init__(self, *args, **kwargs): """Load LDAP settings.""" - parameters.apply_to_django_settings() + param_tools.apply_to_django_settings() super(LDAPBackend, self).__init__(*args, **kwargs) def get_or_create_user(self, username, ldap_user): @@ -47,8 +46,9 @@ try: ldap_user.attrs contains all of their LDAP attributes. """ group = 'SimpleUsers' - admin_groups = parameters \ - .get_admin('LDAP_ADMIN_GROUPS', app='core').split(';') + admin_groups = ( + param_tools.get_global_parameter( + "ldap_admin_groups", app="core").split(";")) for grp in admin_groups: if grp.strip() in ldap_user.group_names: group = 'DomainAdmins' @@ -75,7 +75,8 @@ try: return user def authenticate(self, username, password): - auth_type = parameters.get_admin("AUTHENTICATION_TYPE", app="core") + auth_type = param_tools.get_global_parameter( + "authentication_type", app="core") if auth_type == "ldap": return super(LDAPBackend, self).authenticate( username, password) diff --git a/modoboa/lib/cryptutils.py b/modoboa/lib/cryptutils.py index c98ebff1a..f0401ded7 100644 --- a/modoboa/lib/cryptutils.py +++ b/modoboa/lib/cryptutils.py @@ -7,7 +7,7 @@ import string from Crypto.Cipher import AES -from modoboa.lib import parameters +from modoboa.parameters import tools as param_tools def random_key(l=16): @@ -25,7 +25,7 @@ def random_key(l=16): def encrypt(clear): - key = parameters.get_admin("SECRET_KEY", app="core") + key = param_tools.get_global_parameter("secret_key", app="core") obj = AES.new(key, AES.MODE_ECB) if type(clear) is unicode: clear = clear.encode("utf-8") @@ -38,7 +38,8 @@ def encrypt(clear): def decrypt(ciph): obj = AES.new( - parameters.get_admin("SECRET_KEY", app="core"), AES.MODE_ECB + param_tools.get_global_parameter( + "secret_key", app="core"), AES.MODE_ECB ) ciph = base64.b64decode(ciph) clear = obj.decrypt(ciph) diff --git a/modoboa/lib/ldap_utils.py b/modoboa/lib/ldap_utils.py index 42db29d44..60b4b5f44 100644 --- a/modoboa/lib/ldap_utils.py +++ b/modoboa/lib/ldap_utils.py @@ -1,8 +1,7 @@ # coding: utf-8 """ -:mod:`ldaputils` --- a collection of LDAP based classes/functions ------------------------------------------------------------------ +A collection of LDAP based classes/functions. For a first version, the LDAP support offered by Modoboa only supports one global server definition : the one the django-auth-ldap uses. @@ -39,14 +38,15 @@ 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 +from modoboa.parameters import tools as param_tools class LDAPAuthBackend(object): + """LDAP authentication backend.""" def __init__(self): - parameters.apply_to_django_settings() + param_tools.apply_to_django_settings() self.server_uri = self._setting( "AUTH_LDAP_SERVER_URI", "ldap://localhost" ) @@ -74,7 +74,7 @@ class LDAPAuthBackend(object): """Connect to the server according to configuration.""" if self.conn is not None: return - mode = parameters.get_admin("LDAP_AUTH_METHOD", app="core") + mode = param_tools.get_global_parameter("ldap_auth_method", app="core") if mode == "searchbind": bind_dn = self._setting("AUTH_LDAP_BIND_DN", "") bind_pwd = self._setting("AUTH_LDAP_BIND_PASSWORD", "") @@ -86,8 +86,9 @@ class LDAPAuthBackend(object): def _find_user_dn(self, user): """Find the DN of the given user.""" - sbase = parameters.get_admin("LDAP_SEARCH_BASE", app="core") - sfilter = parameters.get_admin("LDAP_SEARCH_FILTER", app="core") + core_params = param_tools.get_global_parameter("core") + sbase = core_params["ldap_search_base"] + sfilter = core_params["ldap_search_filter"] sfilter = sfilter % {"user": user} res = self.conn.search_s(sbase, ldap.SCOPE_SUBTREE, sfilter) try: @@ -102,7 +103,8 @@ class LDAPAuthBackend(object): :param clearpassword: the clear password :return: the encrypted password """ - scheme = parameters.get_admin("PASSWORD_SCHEME", app="core") + scheme = param_tools.get_global_parameter( + "password_scheme", app="core") hasher = get_password_hasher(scheme.upper())('ldap') return hasher.encrypt(clearpassword) diff --git a/modoboa/lib/listing.py b/modoboa/lib/listing.py index 3ae82d832..e8f38f84d 100644 --- a/modoboa/lib/listing.py +++ b/modoboa/lib/listing.py @@ -1,6 +1,6 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger -from modoboa.lib import parameters +from modoboa.parameters import tools as param_tools def get_sort_order(qdict, default, allowed_values=None): @@ -34,7 +34,8 @@ def get_listing_page(objects, pagenum): :return: a ``Page`` object """ paginator = Paginator( - objects, int(parameters.get_admin("ITEMS_PER_PAGE", app="core")), + objects, + param_tools.get_global_parameter("items_per_page", app="core"), allow_empty_first_page=False ) try: diff --git a/modoboa/lib/parameters.py b/modoboa/lib/parameters.py deleted file mode 100644 index 4efbdead1..000000000 --- a/modoboa/lib/parameters.py +++ /dev/null @@ -1,325 +0,0 @@ -# coding: utf-8 - -""" -This interface provides a simple way to declare and store parameters -in Modoboa's database. - -Core components or extensions can register their own parameters, which -will be available and modifiable directly from the web interface. - -Only super users will be able to access this part of the web interface. -""" - -from django import forms - -from modoboa.core import parameters as core_parameters -from modoboa.lib import events -from modoboa.lib import signals -from modoboa.lib.sysutils import guess_extension_name - -from . import db_utils -from . import form_utils - - -_params = {'A': {}, 'U': {}} - - -class GenericParametersForm(forms.Form): - """Base class for parameter forms. - - Each extension has the possibility to define global parameters. - """ - app = None - visibility_rules = None - - def __init__(self, *args, **kwargs): - """Constructor.""" - if self.app is None: - raise NotImplementedError - - kwargs["prefix"] = self.app - load_values_from_db = kwargs.pop("load_values_from_db", True) - super(GenericParametersForm, self).__init__(*args, **kwargs) - - self.visirules = {} - if self.visibility_rules is not None: - for key, rule in self.visibility_rules.items(): - field, value = rule.split("=") - visibility = { - "field": "id_%s-%s" % (self.app, field), "value": value - } - self.visirules["%s-%s" % (self.app, key)] = visibility - - if not args and load_values_from_db: - self._load_initial_values() - - def _load_initial_values(self): - raise NotImplementedError - - def _decode_value(self, value): - return value.decode('unicode_escape').replace('\\r\\n', '\n') - - def _load_extra_parameters(self, level): - params = events.raiseDictEvent('GetExtraParameters', self.app, level) - for pname, pdef in params.items(): - self.fields[pname] = pdef - - def _save_parameter(self, p, name, value): - if p.value == value: - return - name = name.lower() - if hasattr(self, "update_%s" % name): - getattr(self, "update_%s" % name)(value) - if type(value) is unicode: - p.value = value.encode("unicode_escape").strip() - else: - p.value = str(value) - p.save() - - @staticmethod - def has_access(**kwargs): - return True - - def save(self): - raise NotImplementedError - - -class AdminParametersForm(GenericParametersForm): - """Base form to declare admin level parameters.""" - - def __init__(self, *args, **kwargs): - """Store LocalConfig instance.""" - self.localconfig = kwargs.pop("localconfig", None) - super(AdminParametersForm, self).__init__(*args, **kwargs) - - def _load_initial_values(self): - """Load form initial values from database.""" - condition = ( - not db_utils.db_table_exists("core_localconfig") or - not self.localconfig) - if condition: - return - values = self.localconfig.parameters.get_values(app=self.app) - for key, value in values: - self.fields[key].initial = value - - def save(self): - """Save parameters to database.""" - parameters = {} - for name, value in self.cleaned_data.items(): - if type(self.fields[name]) is form_utils.SeparatorField: - continue - parameters[name] = value - self.localconfig.parameters.set_values(parameters, app=self.app) - - def to_django_settings(self): - """Inject parameters into django settings module.""" - pass - - def get_current_values(self): - """Return current values from the database.""" - values = {} - for key in self.fields.keys(): - try: - values[key] = get_admin(key.upper(), app=self.app) - except core_parameters.NotDefined: - pass - return values - - -class UserParametersForm(GenericParametersForm): - - def __init__(self, *args, **kwargs): - self.user = kwargs.pop("user") if "user" in kwargs else None - super(UserParametersForm, self).__init__(*args, **kwargs) - - def _load_initial_values(self): - if not db_utils.db_table_exists("lib_userparameter"): - return - if self.user is None: - return - from .models import UserParameter - - names = [ - "%s.%s" % (self.app, name.upper()) for name in self.fields.keys() - ] - for p in UserParameter.objects.filter(user=self.user, name__in=names): - self.fields[p.shortname].initial = self._decode_value(p.value) - - def save(self): - from .models import UserParameter - from modoboa.lib.form_utils import SeparatorField - - for name, value in self.cleaned_data.items(): - if type(self.fields[name]) is SeparatorField: - continue - fullname = "%s.%s" % (self.app, name.upper()) - try: - p = UserParameter.objects.get(user=self.user, name=fullname) - except UserParameter.DoesNotExist: - p = UserParameter() - p.user = self.user - p.name = fullname - self._save_parameter(p, name, value) - - -def register(formclass, label): - """Register a form class containing parameters. - - formclass must inherit from ``AdminParametersForm`` or - ``UserParametersForm``. - - :param formclass: a form class - :param string label: the label to display in parameters or settings pages - """ - if issubclass(formclass, AdminParametersForm): - level = "admin" - elif issubclass(formclass, UserParametersForm): - level = "user" - else: - raise RuntimeError("Unknown parameter class") - core_parameters.registry.add(level, formclass, label) - - -def unregister(app=None): - """Unregister an application - - All parameters associated to this application will also be - removed. - - :param app: the application's name (string) - """ - if app is None: - app = guess_extension_name() - for lvlparams in _params.values(): - if app in lvlparams: - del lvlparams[app] - - -def __is_defined(app, level, name): - not_defined = ( - level not in ['A', 'U'] or - app not in _params[level] or - name not in _params[level][app]["defaults"] - ) - if not_defined: - raise NotDefined(app, name) - - -def save_admin(name, value, app=None): - """Set a new value for a given parameter.""" - from .models import Parameter - - if app is None: - app = guess_extension_name() - __is_defined(app, 'A', name) - fullname = "%s.%s" % (app, name) - try: - p = Parameter.objects.get(name=fullname) - except Parameter.DoesNotExist: - p = Parameter() - p.name = fullname - p.value = None - f = get_parameter_form('A', name, app) - f()._save_parameter(p, name, value) - - -def save_user(user, name, value, app=None): - from .models import UserParameter - - if app is None: - app = guess_extension_name() - __is_defined(app, 'U', name) - fullname = "%s.%s" % (app, name) - try: - p = UserParameter.objects.get(user=user, name=fullname) - except UserParameter.DoesNotExist: - p = UserParameter() - p.user = user - p.name = fullname - f = get_parameter_form('U', name, app) - f()._save_parameter(p, name, value) - - -def get_admin(name, app=None, raise_error=True): - """Return an administrative parameter. - - A ``NotDefined`` exception if the parameter doesn't exist. - - :param name: the parameter's name - :param app: the application owning the parameter - :return: the corresponding value as a string - """ - # FIXME: deprecate this function - request = signals.get_request() - if request: - localconfig = request.localconfig - else: - from modoboa.core import models as core_models - localconfig = core_models.LocalConfig.objects.first() - if app is None: - app = guess_extension_name() - return localconfig.parameters.get_value( - name, app=app, raise_exception=raise_error) - - -def get_user(user, name, app=None, raise_error=True): - """Return a parameter for a specific user - - A ``NotDefined`` exception if the parameter doesn't exist. - - :param ``User`` user: the desired user - :param name: the parameter's name - :param app: the application owning the parameter - :return: the corresponding value as a string - """ - from .models import UserParameter - - if app is None: - app = guess_extension_name() - try: - __is_defined(app, "U", name) - except NotDefined: - if raise_error: - raise - return None - try: - p = UserParameter.objects.get(user=user, name="%s.%s" % (app, name)) - except UserParameter.DoesNotExist: - return _params["U"][app]["defaults"][name] - return p.value.decode("unicode_escape") - - -def get_admin_forms(*args, **kwargs): - """Get all admin level forms. - - Generates an instance of each declared form. - """ - return core_parameters.registry.get_forms( - "admin", *args, **kwargs) - - -def get_user_forms(user, *args, **kwargs): - """Return an instance of each user-level forms.""" - kwargs.update({"user": user, "first_app": "general"}) - return core_parameters.registry.get_forms( - "user", *args, **kwargs) - - -def get_parameter_form(level, name, app=None): - """Return the form containing a specific parameter - - :param string level: associated level - :param string name: paremeter's name - :param string app: parameter's application - :return: a form class - """ - if app is None: - app = guess_extension_name() - __is_defined(app, level, name) - return _params[level][app]["form"] - - -def apply_to_django_settings(): - for form in get_admin_forms(): - form["form"].to_django_settings() diff --git a/modoboa/lib/tests.py b/modoboa/lib/tests.py index f36ea16b9..c2544717c 100644 --- a/modoboa/lib/tests.py +++ b/modoboa/lib/tests.py @@ -3,14 +3,12 @@ import json -from django import forms from django.core import management from django.test import TestCase from rest_framework.authtoken.models import Token from rest_framework.test import APITestCase -from modoboa.lib import parameters from modoboa.core import models as core_models from . import sysutils @@ -96,76 +94,3 @@ class ModoAPITestCase(ParametersMixin, APITestCase): """Setup.""" super(ModoAPITestCase, self).setUp() self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token.key) - - -class TestParams(parameters.AdminParametersForm): - app = "test" - - param1 = forms.CharField(label="Test", initial="toto") - param2 = forms.IntegerField(label="Int", initial=42) - - def update_param2(self, value): - raise RuntimeError("new value %d" % value) - - -class TestUserParams(parameters.UserParametersForm): - app = "test" - - param1 = forms.CharField(label="Test", initial="titi") - param2 = forms.IntegerField(label="Int", initial=42) - - def update_param2(self, value): - raise RuntimeError("new value %d" % value) - - -class ParameterTestCase(TestCase): - """Simple test cases for ``modoboa.lib.parameters`` module. - """ - - @classmethod - def setUpTestData(cls): - super(ParameterTestCase, cls).setUpTestData() - cls.user = core_models.User.objects.create(username="tester") - - def setUp(self): - """Initialize tests.""" - super(ParameterTestCase, self).setUp() - parameters.register(TestParams, "Test") - parameters.register(TestUserParams, "TestUser") - - def test_register_form(self): - self.assertIn("test", parameters._params['A']) - - def test_unregister(self): - parameters.unregister("test") - self.assertNotIn("test", parameters._params['A']) - - def test_get_parameter_form(self): - self.assertIs( - parameters.get_parameter_form('A', 'PARAM1', 'test'), - TestParams - ) - - def test_save_admin(self): - parameters.save_admin("PARAM1", "45", app="test") - self.assertEqual(parameters.get_admin("PARAM1", app="test"), "45") - - def test_update_param(self): - with self.assertRaisesRegexp(RuntimeError, "new value 35"): - parameters.save_admin("PARAM2", 35, app="test") - - def test_get_admin(self): - self.assertEqual(parameters.get_admin("PARAM1", "test"), "toto") - with self.assertRaises(parameters.NotDefined): - parameters.get_admin("TOTO", "test") - - def test_get_user(self): - self.assertEqual( - parameters.get_user(self.user, "PARAM1", "test"), "titi") - with self.assertRaises(parameters.NotDefined): - parameters.get_user(self.user, "TOTO", "test") - - def test_save_user(self): - parameters.save_user(self.user, "PARAM1", "pouet", "test") - self.assertEqual( - parameters.get_user(self.user, "PARAM1", "test"), "pouet") diff --git a/modoboa/limits/app_settings.py b/modoboa/limits/app_settings.py index f94c17d10..b194f688a 100644 --- a/modoboa/limits/app_settings.py +++ b/modoboa/limits/app_settings.py @@ -4,10 +4,10 @@ from django import forms from django.utils.translation import ugettext_lazy as _ from modoboa.lib.form_utils import SeparatorField, YesNoField -from modoboa.lib.parameters import AdminParametersForm +from modoboa.parameters import forms as param_forms -class ParametersForm(AdminParametersForm): +class ParametersForm(param_forms.AdminParametersForm): """Available admin parameters.""" app = "limits" @@ -135,7 +135,3 @@ class ParametersForm(AdminParametersForm): "deflt_domain_domain_aliases_limit": "enable_domain_limits=True", "deflt_domain_domain_admins_limit": "enable_domain_limits=True", } - - def __init__(self, *args, **kwargs): - super(ParametersForm, self).__init__(*args, **kwargs) - self._load_extra_parameters('A') diff --git a/modoboa/limits/apps.py b/modoboa/limits/apps.py index 37d31581f..1152b3b8b 100644 --- a/modoboa/limits/apps.py +++ b/modoboa/limits/apps.py @@ -1,20 +1,20 @@ """App config for limits.""" from django.apps import AppConfig - +from django.utils.translation import ugettext as _ EVENTS = [ - 'GetExtraLimitTemplates' + "GetExtraLimitTemplates" ] def load_limits_settings(): """Load settings.""" - from django.utils.translation import ugettext as _ - from modoboa.lib import events, parameters + from modoboa.lib import events + from modoboa.parameters import tools as param_tools from .app_settings import ParametersForm - parameters.register(ParametersForm, _("Limits")) + param_tools.registry.add("global", ParametersForm, _("Limits")) events.declare(EVENTS) from . import controls from . import general_callbacks diff --git a/modoboa/limits/controls.py b/modoboa/limits/controls.py index 8a5325e4d..8a1a9cfdb 100644 --- a/modoboa/limits/controls.py +++ b/modoboa/limits/controls.py @@ -1,18 +1,16 @@ # coding: utf-8 -""" -:mod:`controls` --- provides event handlers that check if limits are reached ----------------------------------------------------------------------------- -""" +"""Event handlers that check if limits are reached.""" -from modoboa.lib import events, parameters +from modoboa.lib import events from modoboa.lib.permissions import get_object_owner +from modoboa.parameters import tools as param_tools from . import utils def move_pool_resource(owner, user): - """Move resource from one pool to another + """Move resource from one pool to another. When an account doesn't need a pool anymore, we move the associated resource to the pool of its owner. @@ -40,7 +38,7 @@ def user_can_set_role(user, role, account=None): :param str newrole: role to check """ condition = ( - not parameters.get_admin("ENABLE_ADMIN_LIMITS") or + not param_tools.get_global_parameter("enable_admin_limits") or role != "DomainAdmins") if condition: return [True] diff --git a/modoboa/limits/general_callbacks.py b/modoboa/limits/general_callbacks.py index f7abf0475..71da5c367 100644 --- a/modoboa/limits/general_callbacks.py +++ b/modoboa/limits/general_callbacks.py @@ -3,7 +3,8 @@ from django.template.loader import render_to_string from django.utils.translation import ugettext as _ -from modoboa.lib import events, parameters +from modoboa.lib import events +from modoboa.parameters import tools as param_tools from . import forms from . import utils @@ -12,7 +13,7 @@ from . import utils @events.observe("ExtraAdminContent") def display_pool_usage(user, target, currentpage): condition = ( - not parameters.get_admin("ENABLE_ADMIN_LIMITS") or + not param_tools.get_global_parameter("enable_admin_limits") or target != "leftcol" or user.is_superuser) if condition: return [] @@ -40,7 +41,7 @@ def display_pool_usage(user, target, currentpage): @events.observe("ExtraAccountForm") def extra_account_form(user, account=None): - if not parameters.get_admin("ENABLE_ADMIN_LIMITS"): + if not param_tools.get_global_parameter("enable_admin_limits"): return [] if user.role not in ["SuperAdmins", "Resellers"]: return [] @@ -61,7 +62,7 @@ def extra_account_form(user, account=None): @events.observe("ExtraDomainForm") def extra_domain_form(user, domain): """Include domain limits form.""" - if not parameters.get_admin("ENABLE_DOMAIN_LIMITS"): + if not param_tools.get_global_parameter("enable_domain_limits"): return [] if not user.has_perm("admin.change_domain"): return [] @@ -74,7 +75,7 @@ def extra_domain_form(user, domain): @events.observe("FillDomainInstances") def fill_domain_instances(user, domain, instances): """Set domain instance for resources form.""" - if not parameters.get_admin("ENABLE_DOMAIN_LIMITS"): + if not param_tools.get_global_parameter("enable_domain_limits"): return if not user.has_perm("admin.change_domain"): return @@ -93,7 +94,7 @@ def check_form_access(account, form): @events.observe("FillAccountInstances") def fill_account_instances(user, account, instances): condition = ( - not parameters.get_admin("ENABLE_ADMIN_LIMITS") or + not param_tools.get_global_parameter("enable_admin_limits") or (not user.is_superuser and user.role != "Resellers") ) if condition: @@ -106,7 +107,7 @@ def fill_account_instances(user, account, instances): @events.observe("GetStaticContent") def get_static_content(caller, st_type, user): condition = ( - not parameters.get_admin("ENABLE_ADMIN_LIMITS") or + not param_tools.get_global_parameter("enable_admin_limits") or caller not in ["domains", "identities"] or user.role in ["SuperAdmins", "SimpleUsers"] ) diff --git a/modoboa/limits/handlers.py b/modoboa/limits/handlers.py index af063501b..33164b1b7 100644 --- a/modoboa/limits/handlers.py +++ b/modoboa/limits/handlers.py @@ -10,7 +10,7 @@ from modoboa.admin import signals as admin_signals from modoboa.core import models as core_models from modoboa.core import signals as core_signals from modoboa.lib import signals as lib_signals -from modoboa.lib import parameters +from modoboa.parameters import tools as param_tools from . import lib from . import models @@ -21,13 +21,13 @@ from . import utils def check_object_limit(sender, context, object_type, **kwargs): """Check if user can create a new object.""" if context.__class__.__name__ == "User": - if not parameters.get_admin("ENABLE_ADMIN_LIMITS"): + if not param_tools.get_global_parameter("enable_admin_limits"): return if context.is_superuser: return True limit = context.userobjectlimit_set.get(name=object_type) elif context.__class__.__name__ == "Domain": - if not parameters.get_admin("ENABLE_DOMAIN_LIMITS"): + if not param_tools.get_global_parameter("enable_domain_limits"): return limit = context.domainobjectlimit_set.get(name=object_type) else: @@ -50,9 +50,9 @@ def create_user_limits(sender, instance, **kwargs): max_value = 0 # creator can be None if user was created by a factory if not creator or creator.is_superuser: - max_value = int( - parameters.get_admin( - "DEFLT_USER_{}_LIMIT".format(name.upper()))) + max_value = ( + param_tools.get_global_parameter( + "deflt_user_{0}_limit".format(name))) models.UserObjectLimit.objects.create( user=instance, name=name, content_type=ct, max_value=max_value) @@ -63,8 +63,9 @@ def create_domain_limits(sender, instance, **kwargs): if not kwargs.get("created"): return for name, definition in utils.get_domain_limit_templates(): - max_value = int( - parameters.get_admin("DEFLT_DOMAIN_{}_LIMIT".format(name.upper()))) + max_value = ( + param_tools.get_global_parameter( + "deflt_domain_{0}_limit".format(name))) models.DomainObjectLimit.objects.create( domain=instance, name=name, max_value=max_value) @@ -72,7 +73,7 @@ def create_domain_limits(sender, instance, **kwargs): @receiver(admin_signals.extra_domain_dashboard_widgets) def display_domain_limits(sender, user, domain, **kwargs): """Display resources usage for domain.""" - if not parameters.get_admin("ENABLE_DOMAIN_LIMITS"): + if not param_tools.get_global_parameter("enable_domain_limits"): return [] return [{ "column": "right", @@ -87,7 +88,7 @@ def display_domain_limits(sender, user, domain, **kwargs): def display_admin_limits(sender, user, account, **kwargs): """Display resources usage for admin.""" condition = ( - parameters.get_admin("ENABLE_ADMIN_LIMITS") and + param_tools.get_global_parameter("enable_admin_limits") and account.role in ["DomainAdmins", "Resellers"] ) if not condition: diff --git a/modoboa/parameters/__init__.py b/modoboa/parameters/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modoboa/parameters/apps.py b/modoboa/parameters/apps.py new file mode 100644 index 000000000..cbf060639 --- /dev/null +++ b/modoboa/parameters/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class ParametersConfig(AppConfig): + name = 'parameters' diff --git a/modoboa/parameters/forms.py b/modoboa/parameters/forms.py new file mode 100644 index 000000000..21243fe77 --- /dev/null +++ b/modoboa/parameters/forms.py @@ -0,0 +1,107 @@ +"""Parameters forms.""" + +from django import forms + +from modoboa.lib import events +from modoboa.lib import db_utils +from modoboa.lib import form_utils + + +class GenericParametersForm(forms.Form): + """Base class for parameter forms. + + Each extension has the possibility to define global parameters. + """ + app = None + visibility_rules = None + + def __init__(self, *args, **kwargs): + """Constructor.""" + if self.app is None: + raise NotImplementedError + + kwargs["prefix"] = self.app + load_values_from_db = kwargs.pop("load_values_from_db", True) + super(GenericParametersForm, self).__init__(*args, **kwargs) + + self.visirules = {} + if self.visibility_rules is not None: + for key, rule in self.visibility_rules.items(): + field, value = rule.split("=") + visibility = { + "field": "id_%s-%s" % (self.app, field), "value": value + } + self.visirules["%s-%s" % (self.app, key)] = visibility + + if not args and load_values_from_db: + self._load_initial_values() + + def _load_initial_values(self): + raise NotImplementedError + + @staticmethod + def has_access(**kwargs): + return True + + def save(self): + raise NotImplementedError + + +class AdminParametersForm(GenericParametersForm): + """Base form to declare admin level parameters.""" + + def __init__(self, *args, **kwargs): + """Store LocalConfig instance.""" + self.localconfig = kwargs.pop("localconfig", None) + super(AdminParametersForm, self).__init__(*args, **kwargs) + + def _load_initial_values(self): + """Load form initial values from database.""" + condition = ( + not db_utils.db_table_exists("core_localconfig") or + not self.localconfig) + if condition: + return + values = self.localconfig.parameters.get_values(app=self.app) + for key, value in values: + self.fields[key].initial = value + + def save(self): + """Save parameters to database.""" + parameters = {} + for name, value in self.cleaned_data.items(): + if type(self.fields[name]) is form_utils.SeparatorField: + continue + parameters[name] = value + self.localconfig.parameters.set_values(parameters, app=self.app) + + def to_django_settings(self): + """Inject parameters into django settings module.""" + pass + + +class UserParametersForm(GenericParametersForm): + """Base form to declare user level parameters.""" + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop("user", None) + super(UserParametersForm, self).__init__(*args, **kwargs) + + def _load_initial_values(self): + """Load initial values from User instance.""" + if not db_utils.db_table_exists("core_localconfig"): + return + if self.user is None: + return + parameters = self.user.parameters.get_values(self.app) + for parameter, value in parameters: + self.fields[parameter].initial = value + + def save(self): + """Save new values.""" + parameters = {} + for name, value in self.cleaned_data.items(): + if type(self.fields[name]) is form_utils.SeparatorField: + continue + parameters[name] = value + self.user.parameters.set_values(parameters, app=self.app) diff --git a/modoboa/templates/common/parameters.html b/modoboa/parameters/templates/parameters/base_form.html similarity index 100% rename from modoboa/templates/common/parameters.html rename to modoboa/parameters/templates/parameters/base_form.html diff --git a/modoboa/parameters/tests.py b/modoboa/parameters/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/modoboa/parameters/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/modoboa/core/parameters.py b/modoboa/parameters/tools.py similarity index 74% rename from modoboa/core/parameters.py rename to modoboa/parameters/tools.py index bf65c3b14..b0202c913 100644 --- a/modoboa/core/parameters.py +++ b/modoboa/parameters/tools.py @@ -2,6 +2,7 @@ from modoboa.lib import exceptions from modoboa.lib import form_utils +from modoboa.lib import signals from modoboa.lib.sysutils import guess_extension_name @@ -27,7 +28,7 @@ class Registry(object): def __init__(self): """Constructor.""" self._registry = { - "admin": {}, + "global": {}, "user": {} } @@ -123,7 +124,9 @@ class Manager(object): app = guess_extension_name() values = registry.get_defaults(self._level, app) for parameter, value in values.iteritems(): - yield (parameter, self._parameters[app].get(parameter, value)) + if app in self._parameters: + value = self._parameters[app].get(parameter, value) + yield (parameter, value) def set_value(self, parameter, value, app=None): """Set parameter for the given app.""" @@ -144,3 +147,52 @@ class Manager(object): if app not in self._parameters: self._parameters[app] = registry.get_defaults(self._level, app) self._parameters[app].update(values) + + +def get_localconfig(): + """Retrieve current LocalConfig instance.""" + from modoboa.core import models as core_models + request = signals.get_request() + if request: + return request.localconfig + return core_models.LocalConfig.objects.first() + + +def get_global_parameter(name, app=None, **kwargs): + """Retrieve a global parameter. + + This is a shortcut to use when a LocalConfig instance is not + available (ie. when no request object is available). + + A ``NotDefined`` exception if the parameter doesn't exist. + + :param name: the parameter's name + :param app: the application owning the parameter + :return: the corresponding value + + """ + if app is None: + app = guess_extension_name() + return get_localconfig().parameters.get_value(name, app=app, **kwargs) + + +def get_global_parameters(app, **kwargs): + """Retrieve global parameters of a given app. + + This is a shortcut to use when a LocalConfig instance is not + available (ie. when no request object is available). + + A ``NotDefined`` exception if the parameter doesn't exist. + + :param name: the parameter's name + :param app: the application owning the parameter + :return: the corresponding value + + """ + return get_localconfig().parameters.get_values(app, **kwargs) + + +def apply_to_django_settings(): + """Apply global parameters to Django settings module.""" + for form in registry.get_forms("global"): + form["form"].to_django_settings() diff --git a/modoboa/relaydomains/app_settings.py b/modoboa/relaydomains/app_settings.py index 054c78329..a6cf7fb4d 100644 --- a/modoboa/relaydomains/app_settings.py +++ b/modoboa/relaydomains/app_settings.py @@ -3,11 +3,11 @@ from django import forms from django.utils.translation import ugettext_lazy -from modoboa.lib import parameters from modoboa.lib.form_utils import SeparatorField +from modoboa.parameters import forms as param_forms -class AdminParametersForm(parameters.AdminParametersForm): +class AdminParametersForm(param_forms.AdminParametersForm): """Admin parameters definition.""" app = "relaydomains" diff --git a/modoboa/relaydomains/apps.py b/modoboa/relaydomains/apps.py index 2a8895b46..433f5d770 100644 --- a/modoboa/relaydomains/apps.py +++ b/modoboa/relaydomains/apps.py @@ -1,7 +1,7 @@ """AppConfig for relaydomains.""" from django.apps import AppConfig - +from django.utils.translation import ugettext as _ EVENTS = [ "RelayDomainCreated", @@ -16,11 +16,11 @@ EVENTS = [ def load_relaydomains_settings(): """Load application settings.""" - from django.utils.translation import ugettext as _ - from modoboa.lib import events, parameters + from modoboa.parameters import tools as param_tools + from modoboa.lib import events from .app_settings import AdminParametersForm - parameters.register(AdminParametersForm, _("Relay domains")) + param_tools.registry.add("global", AdminParametersForm, _("Relay domains")) events.declare(EVENTS) from . import general_callbacks diff --git a/modoboa/relaydomains/models.py b/modoboa/relaydomains/models.py index 8beb5e1fa..e89d528f2 100644 --- a/modoboa/relaydomains/models.py +++ b/modoboa/relaydomains/models.py @@ -7,8 +7,8 @@ from django.utils.translation import ugettext as _, ugettext_lazy from reversion import revisions as reversion from modoboa.admin import models as admin_models -from modoboa.lib import parameters from modoboa.lib.exceptions import BadRequest +from modoboa.parameters import tools as param_tools class ServiceManager(Manager): @@ -20,7 +20,7 @@ class ServiceManager(Manager): entries are saved and outdated ones (ie. present in the database but not in the file) are removed. """ - with open(parameters.get_admin('MASTER_CF_PATH')) as fp: + with open(param_tools.get_global_parameter("master_cf_path")) as fp: content = fp.read() services = [] for line in content.split('\n'):