Merge pull request #954 from tonioo/refactor/parameters

Refactor/parameters
This commit is contained in:
Antoine Nguyen
2016-11-04 18:10:12 +01:00
committed by GitHub
71 changed files with 1071 additions and 907 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ before_script:
script:
- python ./tests.py
- cd test_project
- coverage run --source ../modoboa manage.py test modoboa.core modoboa.lib modoboa.admin.tests modoboa.limits modoboa.relaydomains
- coverage run --source ../modoboa manage.py test modoboa.core modoboa.admin.tests modoboa.limits modoboa.relaydomains
after_success:
- codecov
-14
View File
@@ -563,20 +563,6 @@ Must return a list of set. Each set must contain at least three entries::
[('<limit_name>', '<limit label>', '<limit help text>')]
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
+13 -13
View File
@@ -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,14 +48,14 @@ ADMIN_EVENTS = [
]
class AdminParametersForm(parameters.AdminParametersForm):
class AdminParametersForm(param_forms.AdminParametersForm):
app = "admin"
dom_sep = SeparatorField(label=ugettext_lazy("Domains"))
enable_mx_checks = YesNoField(
label=ugettext_lazy("Enable MX checks"),
initial="yes",
initial=True,
help_text=ugettext_lazy(
"Check that every domain has a valid MX record"
)
@@ -75,7 +75,7 @@ class AdminParametersForm(parameters.AdminParametersForm):
enable_dnsbl_checks = YesNoField(
label=ugettext_lazy("Enable DNSBL checks"),
initial="yes",
initial=True,
help_text=ugettext_lazy(
"Check every domain against major DNSBL providers"
)
@@ -85,7 +85,7 @@ class AdminParametersForm(parameters.AdminParametersForm):
handle_mailboxes = YesNoField(
label=ugettext_lazy("Handle mailboxes on filesystem"),
initial="no",
initial=False,
help_text=ugettext_lazy(
"Rename or remove mailboxes on the filesystem when they get"
" renamed or removed within Modoboa"
@@ -111,14 +111,14 @@ class AdminParametersForm(parameters.AdminParametersForm):
auto_account_removal = YesNoField(
label=ugettext_lazy("Automatic account removal"),
initial="no",
initial=False,
help_text=ugettext_lazy(
"When a mailbox is removed, also remove the associated account")
)
auto_create_domain_and_mailbox = YesNoField(
label=ugettext_lazy("Automatic domain/mailbox creation"),
initial="yes",
initial=True,
help_text=ugettext_lazy(
"Create a domain and a mailbox when an account is automatically "
"created."
@@ -127,8 +127,8 @@ class AdminParametersForm(parameters.AdminParametersForm):
# Visibility rules
visibility_rules = {
"valid_mxs": "enable_mx_checks=yes",
"mailboxes_owner": "handle_mailboxes=yes",
"valid_mxs": "enable_mx_checks=True",
"mailboxes_owner": "handle_mailboxes=True",
}
def __init__(self, *args, **kwargs):
@@ -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
+3 -2
View File
@@ -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 parameters.get_admin("AUTO_CREATE_DOMAIN_AND_MAILBOX") == "no":
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:
+5 -4
View File
@@ -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"]
+26 -17
View File
@@ -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.
@@ -164,7 +166,7 @@ class DomainFormOptions(forms.Form):
create_dom_admin = YesNoField(
label=ugettext_lazy("Create a domain administrator"),
initial="no",
initial=False,
help_text=ugettext_lazy(
"Automatically create an administrator for this domain"
)
@@ -182,7 +184,7 @@ class DomainFormOptions(forms.Form):
create_aliases = YesNoField(
label=ugettext_lazy("Create aliases"),
initial="yes",
initial=True,
help_text=ugettext_lazy(
"Automatically create standard aliases for this domain"
),
@@ -191,25 +193,33 @@ class DomainFormOptions(forms.Form):
def __init__(self, user, *args, **kwargs):
super(DomainFormOptions, self).__init__(*args, **kwargs)
result = events.raiseQueryEvent('UserCanSetRole', user, 'DomainAdmins')
result = events.raiseQueryEvent("UserCanSetRole", user, "DomainAdmins")
if False in result:
self.fields = {}
return
if args:
if args[0].get("create_dom_admin", "no") == "yes":
self.fields["dom_admin_username"].required = True
self.fields["create_aliases"].required = True
def clean_dom_admin_username(self):
"""Ensure admin username is an email address."""
if '@' in self.cleaned_data["dom_admin_username"]:
if "@" in self.cleaned_data["dom_admin_username"]:
raise forms.ValidationError(_("Invalid format"))
return self.cleaned_data["dom_admin_username"]
def clean(self):
"""Check required values."""
cleaned_data = super(DomainFormOptions, self).clean()
if cleaned_data.get("create_dom_admin"):
if not cleaned_data.get("dom_admin_username"):
self.add_error(
"dom_admin_username", _("This field is required."))
if "create_aliases" not in cleaned_data:
self.add_error(
"create_aliases", _("This field is required."))
return cleaned_data
def save(self, *args, **kwargs):
if not self.fields:
return
if self.cleaned_data["create_dom_admin"] == "no":
if not self.cleaned_data["create_dom_admin"]:
return
user = kwargs.pop("user")
domain = kwargs.pop("domain")
@@ -237,8 +247,7 @@ class DomainFormOptions(forms.Form):
override_rules=user.has_perm("admin.change_domain"))
mb.save(creator=user)
if domain.type == "domain" and \
self.cleaned_data["create_aliases"] == "yes":
if domain.type == "domain" and self.cleaned_data["create_aliases"]:
core_signals.can_create_object.send(
self.__class__, context=user, object_type="mailbox_aliases")
alias = Alias(
@@ -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 parameters.get_admin("HANDLE_MAILBOXES") == 'no':
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"])
@@ -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") != "no":
if param_tools.get_global_parameter("enable_mx_checks"):
self.check_valid_mx(domain, mx_list, **options)
condition = (
parameters.get_admin("ENABLE_DNSBL_CHECKS") == "no" or
not param_tools.get_global_parameter("enable_dnsbl_checks") or
options["no_dnsbl"] is True)
if condition or not mx_list:
return
+9 -8
View File
@@ -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") == "yes":
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):
+16 -12
View File
@@ -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 hm is None or hm == "no":
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,8 +182,9 @@ class Mailbox(AdminObject):
def rename_dir(self, old_mail_home):
"""Rename local directory if needed."""
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
if hm is None or hm == "no":
hm = param_tools.get_global_parameter(
"handle_mailboxes", raise_exception=False)
if not hm:
return
MailboxOperation.objects.create(
mailbox=self, type="rename", argument=old_mail_home
@@ -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)
if hm is None or hm == "no":
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.
+2 -2
View File
@@ -146,7 +146,7 @@ Domains.prototype = {
* @param {Object} data - options
*/
init_domain_links: function(data) {
var deloptions = (data.handle_mailboxes === "yes") ?
var deloptions = (data.handle_mailboxes) ?
{keepdir: gettext("Do not delete domain directory")} : {};
var warnmsg = (data.auto_account_removal && data.auto_account_removal === "yes")
? gettext("This operation will remove ALL data associated to this domain.")
@@ -329,7 +329,7 @@ Identities.prototype = {
init_identity_links: function(data) {
var deloptions = {};
if (data.handle_mailboxes === "yes") {
if (data.handle_mailboxes) {
deloptions = {keepdir: gettext("Do not delete mailbox directory")};
}
@@ -21,16 +21,16 @@
<h3 class="panel-title">{% trans "Summary" %}</h3>
</div>
<div class="panel-body">
{% if enable_mx_checks == "yes" or enable_dnsbl_checks == "yes" %}
{% if enable_mx_checks or enable_dnsbl_checks %}
<div class="row">
<span class="title">{% trans "DNS status" %}</span>
{% if domain.awaiting_checks %}
<span class="label label-mini label-info">{% trans "Awaiting checks" %}</span>
{% else %}
{% if enable_mx_checks == "yes" %}
{% if enable_mx_checks %}
<span class="label label-mini label-{% if not domain.mxrecord_set.has_valids %}danger{% else %}success{% endif %}"><a href="{% url 'admin:mx_domain_detail' domain.pk %}" data-toggle="ajaxmodal">MX</a></span>
{% endif %}
{% if enable_dnsbl_checks == "yes" %}
{% if enable_dnsbl_checks %}
<span class="label label-mini label-{{ domain.dnsbl_status_color }}"><a href="{% url 'admin:dnsbl_domain_detail' domain.pk %}" data-toggle="ajaxmodal">DNSBL</a></span>
{% endif %}
{% endif %}
+1 -1
View File
@@ -34,7 +34,7 @@ $(document).ready(function() {
<th id="name" class="sortable" data-sort_order="name" width="">{% trans "Name" %}</th>
<th id="aliases" width="">{% trans "Alias(es)" %}</th>
<th id="tags" class="sortable" data-sort_order="tags" width="">{% trans "Tags" %}</th>
{% if enable_mx_checks == "yes" or enable_dnsbl_checks == "yes" %}
{% if enable_mx_checks or enable_dnsbl_checks %}
<th id="dns" width="">{% trans "DNS status" %}</th>
{% endif %}
<th id="actions" width="">{% trans "Actions" %}</th>
@@ -11,10 +11,10 @@
{% if domain.awaiting_checks %}
<span class="label label-mini label-info">{% trans "Awaiting checks" %}</span>
{% else %}
{% if enable_mx_checks == "yes" %}
{% if enable_mx_checks %}
<span class="label label-mini label-{% if not domain.mxrecord_set.has_valids %}danger{% else %}success{% endif %}"><a href="{% url 'admin:mx_domain_detail' domain.pk %}" data-toggle="ajaxmodal">MX</a></span>
{% endif %}
{% if enable_dnsbl_checks == "yes" %}
{% if enable_dnsbl_checks %}
<span class="label label-mini label-{{ domain.dnsbl_status_color }}"><a href="{% url 'admin:dnsbl_domain_detail' domain.pk %}" data-toggle="ajaxmodal">DNSBL</a></span>
{% endif %}
{% endif %}
+8 -13
View File
@@ -8,9 +8,9 @@ from django.test import override_settings
from modoboa.core import factories as core_factories
from modoboa.core.tests import test_core
from modoboa.core.models import User
from modoboa.lib import parameters
from modoboa.lib.tests import ModoTestCase
from modoboa.lib.tests import NO_LDAP
from modoboa.limits import utils as limits_utils
from .. import factories
from .. import models
@@ -201,8 +201,7 @@ class AccountTestCase(ModoTestCase):
def test_account_detail_view(self):
"""Test account detail view."""
parameters.save_admin(
"ENABLE_ADMIN_LIMITS", "no", app="limits")
self.set_global_parameter("enable_admin_limits", False, app="limits")
account = User.objects.get(username="admin@test.com")
url = reverse("admin:account_detail", args=[account.pk])
response = self.client.get(url)
@@ -210,8 +209,7 @@ class AccountTestCase(ModoTestCase):
self.assertIn("Summary", response.content)
self.assertIn("Administered domains", response.content)
self.assertNotIn("Resources usage", response.content)
parameters.save_admin(
"ENABLE_ADMIN_LIMITS", "yes", app="limits")
self.set_global_parameter("enable_admin_limits", True, app="limits")
response = self.client.get(url)
self.assertIn("Resources usage", response.content)
@@ -228,7 +226,7 @@ class LDAPAccountTestCase(test_core.LDAPTestCaseMixin, ModoTestCase):
"""Check if objects are not created as expected."""
self.activate_ldap_authentication()
self.searchbind_mode()
parameters.save_admin("AUTO_CREATE_DOMAIN_AND_MAILBOX", "no")
self.set_global_parameter("auto_create_domain_and_mailbox", False)
username = "testuser@example.com"
self.authenticate(username, "test")
self.assertFalse(
@@ -242,15 +240,12 @@ class PermissionsTestCase(ModoTestCase):
@classmethod
def setUpTestData(cls):
"""Create test data."""
from modoboa.lib import parameters
from modoboa.limits import utils as limits_utils
super(PermissionsTestCase, cls).setUpTestData()
parameters = {}
for name, tpl in limits_utils.get_user_limit_templates():
parameters.save_admin(
"DEFLT_USER_{0}_LIMIT".format(name.upper()), 2,
app="limits"
)
parameters["deflt_user_{0}_limit".format(name)] = 2
cls.localconfig.parameters.set_values(parameters, app="limits")
cls.localconfig.save()
factories.populate_database()
cls.reseller = core_factories.UserFactory(username="reseller")
cls.reseller.role = "Resellers"
+7 -4
View File
@@ -10,7 +10,6 @@ from rest_framework.authtoken.models import Token
from modoboa.admin import models as admin_models
from modoboa.core import models as core_models
from modoboa.lib import parameters
from modoboa.lib.tests import ModoAPITestCase
from .. import factories
@@ -196,8 +195,10 @@ class AccountAPITestCase(ModoAPITestCase):
def setUp(self):
"""Test setup."""
super(AccountAPITestCase, self).setUp()
parameters.save_admin("ENABLE_ADMIN_LIMITS", "no", app="limits")
parameters.save_admin("ENABLE_DOMAIN_LIMITS", "no", app="limits")
self.set_global_parameters({
"enable_admin_limits": False,
"enable_domain_limits": False
}, app="limits")
def test_get_accounts(self):
"""Retrieve a list of accounts."""
@@ -461,7 +462,9 @@ class AliasAPITestCase(ModoAPITestCase):
def setUpTestData(cls):
"""Create test data."""
super(AliasAPITestCase, cls).setUpTestData()
parameters.save_admin("ENABLE_ADMIN_LIMITS", "no", app="limits")
cls.localconfig.parameters.set_value(
"enable_admin_limits", False, app="limits")
cls.localconfig.save()
factories.populate_database()
cls.da_token = Token.objects.create(
user=core_models.User.objects.get(username="admin@test.com"))
+12 -18
View File
@@ -7,7 +7,6 @@ import json
from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.lib import parameters
from modoboa.lib.tests import ModoTestCase
from .. import factories
@@ -15,7 +14,6 @@ from ..models import Domain, Alias
class DomainTestCase(ModoTestCase):
"""Test case for Domain."""
@classmethod
@@ -27,7 +25,7 @@ class DomainTestCase(ModoTestCase):
def test_create(self):
"""Test the creation of a domain."""
values = {
"name": "pouet.com", "quota": 100, "create_dom_admin": "no",
"name": "pouet.com", "quota": 100, "create_dom_admin": False,
"type": "domain", "stepid": "step3"
}
self.ajax_post(reverse("admin:domain_add"), values)
@@ -40,7 +38,7 @@ class DomainTestCase(ModoTestCase):
def test_create_utf8(self):
"""Test the creation of a domain with non-ASCII characters."""
values = {
"name": "pouét.com", "quota": 100, "create_dom_admin": "no",
"name": "pouét.com", "quota": 100, "create_dom_admin": False,
"type": "domain", "stepid": "step3"
}
self.ajax_post(reverse("admin:domain_add"), values)
@@ -51,12 +49,10 @@ class DomainTestCase(ModoTestCase):
self.assertFalse(dom.admins)
def test_create_with_template(self):
"""Test the creation of a domain with a template
"""
"""Test the creation of a domain with a template."""
values = {
"name": "pouet.com", "quota": 100, "create_dom_admin": "yes",
"dom_admin_username": "toto", "create_aliases": "yes",
"name": "pouet.com", "quota": 100, "create_dom_admin": True,
"dom_admin_username": "toto", "create_aliases": True,
"type": "domain", "stepid": 'step3'
}
self.ajax_post(
@@ -76,8 +72,8 @@ class DomainTestCase(ModoTestCase):
def test_create_with_template_and_empty_quota(self):
"""Test the creation of a domain with a template and no quota"""
values = {
"name": "pouet.com", "quota": 0, "create_dom_admin": "yes",
"dom_admin_username": "toto", "create_aliases": "yes",
"name": "pouet.com", "quota": 0, "create_dom_admin": True,
"dom_admin_username": "toto", "create_aliases": True,
"type": "domain", "stepid": 'step3'
}
self.ajax_post(
@@ -95,10 +91,10 @@ class DomainTestCase(ModoTestCase):
self.assertTrue(da.can_access(al))
def test_create_using_default_quota(self):
parameters.save_admin('DEFAULT_DOMAIN_QUOTA', 50, app='admin')
self.set_global_parameter("default_domain_quota", 50)
values = {
"name": "pouet.com", "create_dom_admin": "yes",
"dom_admin_username": "toto", "create_aliases": "yes",
"name": "pouet.com", "create_dom_admin": True,
"dom_admin_username": "toto", "create_aliases": True,
"type": "domain", "stepid": 'step3'
}
self.ajax_post(
@@ -149,8 +145,7 @@ class DomainTestCase(ModoTestCase):
def test_domain_detail_view(self):
"""Test Domain detail view."""
parameters.save_admin(
"ENABLE_DOMAIN_LIMITS", "no", app="limits")
self.set_global_parameter("enable_domain_limits", False, app="limits")
domain = Domain.objects.get(name="test.com")
url = reverse("admin:domain_detail", args=[domain.pk])
response = self.client.get(url)
@@ -158,8 +153,7 @@ class DomainTestCase(ModoTestCase):
self.assertIn("Summary", response.content)
self.assertIn("Administrators", response.content)
self.assertNotIn("Resources usage", response.content)
parameters.save_admin(
"ENABLE_DOMAIN_LIMITS", "yes", app="limits")
self.set_global_parameter("enable_domain_limits", True, app="limits")
response = self.client.get(url)
self.assertIn("Resources usage", response.content)
+3 -2
View File
@@ -3,7 +3,6 @@ from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.lib import parameters
from modoboa.lib.tests import ModoTestCase
from .. import factories
@@ -16,7 +15,9 @@ class ImportTestCase(ModoTestCase):
def setUpTestData(cls):
"""Create test data."""
super(ImportTestCase, cls).setUpTestData()
parameters.save_admin("ENABLE_ADMIN_LIMITS", "no", app="limits")
cls.localconfig.parameters.set_value(
"enable_admin_limits", False, app="limits")
cls.localconfig.save()
factories.populate_database()
def test_domains_import(self):
+2 -2
View File
@@ -5,7 +5,6 @@ from django.core.urlresolvers import reverse
from django.test import override_settings
from modoboa.lib.tests import ModoTestCase
from modoboa.lib import parameters
from .. import factories
from .. import models
@@ -21,7 +20,8 @@ class MXTestCase(ModoTestCase):
cls.domain = factories.DomainFactory(name="modoboa.org")
# should not exist
cls.bad_domain = factories.DomainFactory(name="pouet.com")
parameters.save_admin("VALID_MXS", "127.0.0.1")
cls.localconfig.parameters.set_value("valid_mxs", "127.0.0.1")
cls.localconfig.save()
models.MXRecord.objects.all().delete()
def test_management_command(self):
+1 -2
View File
@@ -2,7 +2,6 @@ from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.lib.tests import ModoTestCase
from modoboa.lib import parameters
from .. import factories
@@ -28,7 +27,7 @@ class PasswordSchemesTestCase(ModoTestCase):
)
def _test_scheme(self, name, startpattern):
parameters.save_admin('PASSWORD_SCHEME', name, app='core')
self.set_global_parameter("password_scheme", name, app="core")
self._create_account()
account = User.objects.get(username='tester@test.com')
self.assertTrue(account.password.startswith(startpattern))
+14 -10
View File
@@ -16,7 +16,7 @@ from reversion import revisions as reversion
from modoboa.core.models import User
from modoboa.core import signals as core_signals
from modoboa.lib import parameters, events
from modoboa.lib import events
from modoboa.lib.web_utils import (
_render_to_string, render_to_json_response
)
@@ -61,20 +61,22 @@ def _domains(request):
domainlist = sorted(domainlist, key=lambda d: d.tags[0],
reverse=sort_dir == '-')
context = {
"handle_mailboxes": parameters.get_admin(
"HANDLE_MAILBOXES", raise_error=False),
"auto_account_removal": parameters.get_admin("AUTO_ACCOUNT_REMOVAL"),
"handle_mailboxes": request.localconfig.parameters.get_value(
"handle_mailboxes", raise_exception=False),
"auto_account_removal": request.localconfig.parameters.get_value(
"auto_account_removal"),
}
page = get_listing_page(domainlist, request.GET.get("page", 1))
if page is None:
context["length"] = 0
else:
parameters = request.localconfig.parameters
context["rows"] = _render_to_string(
request, "admin/domains_table.html", {
"domains": page.object_list,
"enable_mx_checks": parameters.get_admin("ENABLE_MX_CHECKS"),
"enable_mx_checks": parameters.get_value("enable_mx_checks"),
"enable_dnsbl_checks": (
parameters.get_admin("ENABLE_DNSBL_CHECKS")),
parameters.get_value("enable_dnsbl_checks"))
}
)
context["pages"] = [page.number]
@@ -90,10 +92,11 @@ def domains(request, tplname="admin/domains.html"):
reverse("admin:identity_list")
)
return HttpResponseRedirect(reverse("core:user_index"))
parameters = request.localconfig.parameters
return render(request, tplname, {
"selection": "domains",
"enable_mx_checks": parameters.get_admin("ENABLE_MX_CHECKS"),
"enable_dnsbl_checks": parameters.get_admin("ENABLE_DNSBL_CHECKS")
"enable_mx_checks": parameters.get_value("enable_mx_checks"),
"enable_dnsbl_checks": parameters.get_value("enable_dnsbl_checks")
})
@@ -189,10 +192,11 @@ class DomainDetailView(
context = super(DomainDetailView, self).get_context_data(**kwargs)
result = signals.extra_domain_dashboard_widgets.send(
self.__class__, user=self.request.user, domain=self.object)
parameters = self.request.localconfig.parameters
context.update({
"templates": {"left": [], "right": []},
"enable_mx_checks": parameters.get_admin("ENABLE_MX_CHECKS"),
"enable_dnsbl_checks": parameters.get_admin("ENABLE_DNSBL_CHECKS"),
"enable_mx_checks": parameters.get_value("enable_mx_checks"),
"enable_dnsbl_checks": parameters.get_value("enable_dnsbl_checks"),
})
for receiver, widgets in result:
for widget in widgets:
+3 -3
View File
@@ -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
)
@@ -49,8 +49,8 @@ def _identities(request):
objects = sorted(idents_list, key=lambda o: o.tags[0],
reverse=sort_dir == '-')
context = {
"handle_mailboxes": parameters.get_admin(
"HANDLE_MAILBOXES", raise_error=False)
"handle_mailboxes": request.localconfig.parameters.get_value(
"handle_mailboxes", raise_exception=False)
}
page = get_listing_page(objects, request.GET.get("page", 1))
if page is None:
+15 -22
View File
@@ -3,25 +3,19 @@ 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
def enabled_applications():
"""Return the list of installed extensions.
We check if the table exists before trying to fetch activated
extensions because the admin module is always imported by Django,
even before the database exists (example: the first ``syncdb``).
:return: a list
"""
"""Return the list of installed extensions."""
from modoboa.core.extensions import exts_pool
result = [("user", "user")]
@@ -30,8 +24,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"
@@ -99,7 +92,7 @@ class GeneralParametersForm(parameters.AdminParametersForm):
ldap_secured = YesNoField(
label=ugettext_lazy("Use a secured connection"),
initial="no",
initial=False,
help_text=ugettext_lazy(
"Use an SSL/TLS connection to access the LDAP server")
)
@@ -177,7 +170,7 @@ class GeneralParametersForm(parameters.AdminParametersForm):
ldap_is_active_directory = YesNoField(
label=ugettext_lazy("Active Directory"),
initial="no",
initial=False,
help_text=ugettext_lazy(
"Tell if the LDAP server is an Active Directory one")
)
@@ -224,21 +217,21 @@ class GeneralParametersForm(parameters.AdminParametersForm):
enable_api_communication = YesNoField(
label=ugettext_lazy("Enable communication"),
initial="yes",
initial=True,
help_text=ugettext_lazy(
"Enable communication with Modoboa public API")
)
check_new_versions = YesNoField(
label=ugettext_lazy("Check new versions"),
initial="yes",
initial=True,
help_text=ugettext_lazy(
"Automatically checks if a newer version is available")
)
send_statistics = YesNoField(
label=ugettext_lazy("Send statistics"),
initial="yes",
initial=True,
help_text=ugettext_lazy(
"Send statistics to Modoboa public API "
"(counters and used extensions)")
@@ -296,8 +289,8 @@ class GeneralParametersForm(parameters.AdminParametersForm):
"ldap_admin_groups": "authentication_type=ldap",
"ldap_group_type": "authentication_type=ldap",
"ldap_groups_search_base": "authentication_type=ldap",
"check_new_versions": "enable_api_communication=yes",
"send_statistics": "enable_api_communication=yes",
"check_new_versions": "enable_api_communication=True",
"send_statistics": "enable_api_communication=True",
}
def __init__(self, *args, **kwargs):
@@ -348,7 +341,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 +353,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"):
@@ -369,7 +362,7 @@ class GeneralParametersForm(parameters.AdminParametersForm):
"email": "mail",
"last_name": "sn"
})
ldap_uri = 'ldaps://' if values["ldap_secured"] == "yes" else "ldap://"
ldap_uri = "ldaps://" if values["ldap_secured"] else "ldap://"
ldap_uri += "%s:%s" % (
values["ldap_server_address"], values["ldap_server_port"])
setattr(settings, "AUTH_LDAP_SERVER_URI", ldap_uri)
@@ -400,7 +393,7 @@ class GeneralParametersForm(parameters.AdminParametersForm):
settings, "AUTH_LDAP_USER_DN_TEMPLATE",
values["ldap_user_dn_template"]
)
if values["ldap_is_active_directory"] == "yes":
if values["ldap_is_active_directory"]:
if not hasattr(settings, "AUTH_LDAP_GLOBAL_OPTIONS"):
setattr(settings, "AUTH_LDAP_GLOBAL_OPTIONS", {
ldap.OPT_REFERRALS: False
+5 -5
View File
@@ -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)
@@ -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',
+7 -9
View File
@@ -1,18 +1,16 @@
"""
Custom context processors.
"""
from modoboa.lib import events, parameters
"""Custom context processors."""
from modoboa.lib import events
def top_notifications(request):
"""
A context processor to include top notifications.
"""
"""A context processor to include top notifications."""
if request.user.is_anonymous():
return {}
interval = request.localconfig.parameters.get_value(
"top_notifications_check_interval")
return {
"notifications_check_interval":
int(parameters.get_admin("TOP_NOTIFICATIONS_CHECK_INTERVAL")) * 1000,
"notifications_check_interval": interval * 1000,
"top_notifications": events.raiseQueryEvent(
"TopNotifications", request, False)
}
+4 -7
View File
@@ -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(
@@ -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()
@@ -4,7 +4,7 @@ from django.core.management.base import BaseCommand, CommandError
from modoboa.admin import models as admin_models
from modoboa.core.extensions import exts_pool
from modoboa.lib import api_client, parameters
from modoboa.lib import api_client
from ... import models
@@ -16,18 +16,18 @@ class Command(BaseCommand):
def handle(self, *args, **options):
"""Command entry point."""
if parameters.get_admin("ENABLE_API_COMMUNICATION") == "no":
return
self.client = api_client.ModoAPIClient()
local_config = (
models.LocalConfig.objects.select_related("site").first())
if not local_config.parameters.get_value("enable_api_communication"):
return
self.client = api_client.ModoAPIClient()
if not local_config.api_pk:
pk = self.client.register_instance(local_config.site.domain)
if pk is None:
raise CommandError("Instance registration failed.")
local_config.api_pk = pk
if parameters.get_admin("CHECK_NEW_VERSIONS") == "yes":
if local_config.parameters.get_value("check_new_versions"):
versions = self.client.versions()
if versions is None:
raise CommandError("Failed to retrieve versions from the API.")
@@ -35,7 +35,7 @@ class Command(BaseCommand):
local_config.save()
if parameters.get_admin("SEND_STATISTICS") == "no":
if not local_config.parameters.get_value("send_statistics"):
return
extensions = [ext["name"] for ext in exts_pool.list_all()]
data = {
@@ -10,13 +10,13 @@ from django.core.management.base import BaseCommand
from modoboa.core import PERMISSIONS
from modoboa.core.extensions import exts_pool
from modoboa.core.models import User, ObjectAccess
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
from ... import models
class Command(BaseCommand):
@@ -37,25 +37,26 @@ class Command(BaseCommand):
def handle(self, *args, **options):
"""Command entry point."""
if not User.objects.filter(is_superuser=True).count():
admin = User(username=options["admin_username"], is_superuser=True)
if not models.User.objects.filter(is_superuser=True).count():
admin = models.User(
username=options["admin_username"], is_superuser=True)
admin.set_password("password")
admin.save()
ObjectAccess.objects.create(
models.ObjectAccess.objects.create(
user=admin, content_object=admin, is_owner=True)
param_name = "core.SECRET_KEY"
qset = lib_models.Parameter.objects.filter(name=param_name)
if not qset.exists():
lib_models.Parameter.objects.create(
name=param_name, value=random_key())
lc = models.LocalConfig.objects.first()
secret_key = lc.parameters.get_value("secret_key")
if not secret_key:
lc.parameters.set_value("secret_key", random_key())
lc.save()
for service_name in ['relay', 'smtp']:
for service_name in ["relay", "smtp"]:
relay_models.Service.objects.get_or_create(name=service_name)
exts_pool.load_all()
superadmin = User.objects.filter(is_superuser=True).first()
superadmin = models.User.objects.filter(is_superuser=True).first()
groups = PERMISSIONS.keys() + [
role[0] for role
in events.raiseQueryEvent("GetExtraRoles", superadmin, None)
@@ -75,6 +76,6 @@ class Command(BaseCommand):
extension.load_initial_data()
events.raiseEvent("InitialDataLoaded", extname)
if options['extra_fixtures']:
if options["extra_fixtures"]:
from modoboa.admin import factories
factories.populate_database()
+11
View File
@@ -0,0 +1,11 @@
"""Core middlewares."""
from . import models
class LocalConfigMiddleware(object):
"""A middleware to inject LocalConfig into request."""
def process_request(self, request):
"""Inject LocalConfig instance to request."""
request.localconfig = models.LocalConfig.objects.first()
@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-10-26 08:11
from __future__ import unicode_literals
from django.db import migrations
import jsonfield.fields
def set_parameter(store, parameter):
"""Add parameter to the specified store."""
app, name = parameter.name.split(".")
if app not in store:
store[app] = {}
# decode value
value = parameter.value.decode("unicode_escape")
if value == "yes":
value = True
elif value == "no":
value = False
elif value.isdigit():
value = int(value)
store[app][name.lower()] = value
def move_parameters(apps, schema_editor):
"""Move global and user parameters."""
Parameter = apps.get_model("lib", "Parameter")
LocalConfig = apps.get_model("core", "LocalConfig")
parameters = {}
for parameter in Parameter.objects.all():
set_parameter(parameters, parameter)
LocalConfig.objects.all().update(_parameters=parameters)
User = apps.get_model("core", "User")
for user in User.objects.prefetch_related("userparameter_set"):
for parameter in user.userparameter_set.all():
set_parameter(user._parameters, parameter)
user.save()
def clear_parameters(apps, schema_editor):
"""Reverse operation."""
pass
class Migration(migrations.Migration):
dependencies = [
('core', '0009_auto_20161026_1003'),
('lib', '0005_auto_20160416_1449'),
]
operations = [
migrations.AddField(
model_name='localconfig',
name='_parameters',
field=jsonfield.fields.JSONField(default={}),
preserve_default=False,
),
migrations.AddField(
model_name='user',
name='_parameters',
field=jsonfield.fields.JSONField(default={}),
preserve_default=False,
),
migrations.RunPython(move_parameters, clear_parameters)
]
+22 -7
View File
@@ -20,10 +20,11 @@ import jsonfield
from reversion import revisions as reversion
from modoboa.core.password_hashers import get_password_hasher
from modoboa.lib import events, parameters
from modoboa.lib import events
from modoboa.lib.exceptions import (
PermDeniedException, InternalError, BadRequest, Conflict
)
from modoboa.parameters import tools as param_tools
from . import constants
@@ -67,7 +68,8 @@ class User(PermissionsMixin):
ugettext_lazy('last login'), blank=True, null=True
)
language = models.CharField(ugettext_lazy("language"),
language = models.CharField(
ugettext_lazy("language"),
max_length=10, default="en", choices=constants.LANGUAGES,
help_text=ugettext_lazy(
"Prefered language to display pages."
@@ -81,6 +83,7 @@ class User(PermissionsMixin):
help_text=ugettext_lazy(
"An alternative e-mail address, can be used for recovery needs.")
)
_parameters = jsonfield.JSONField(default={})
objects = UserManager()
@@ -95,6 +98,11 @@ class User(PermissionsMixin):
password_expr = re.compile(r'\{([\w\-]+)\}(.+)')
def __init__(self, *args, **kwargs):
"""Load parameter manager."""
super(User, self).__init__(*args, **kwargs)
self.parameters = param_tools.Manager("user", self._parameters)
def delete(self, fromuser, *args, **kwargs):
"""Custom delete method
@@ -138,13 +146,13 @@ class User(PermissionsMixin):
one more time.
"""
try:
scheme = 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 = 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)
@@ -505,3 +513,10 @@ class LocalConfig(models.Model):
# API results cache
api_versions = jsonfield.JSONField()
_parameters = jsonfield.JSONField(default={})
def __init__(self, *args, **kwargs):
"""Load parameter manager."""
super(LocalConfig, self).__init__(*args, **kwargs)
self.parameters = param_tools.Manager("global", self._parameters)
+8 -5
View File
@@ -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):
+3 -1
View File
@@ -4,6 +4,8 @@
* @constructor
* @param {Object} options - instance options
*/
/* global TwocolsNav */
var Settings = function(options) {
TwocolsNav.call(this, options);
};
@@ -40,7 +42,7 @@ Settings.prototype = {
* Navigation callback: extensions.
*/
extensions_callback: function(data) {
this.select_left_menu();
this.select_left_menu();
if (data.content) {
$('#' + this.options.divid).html(data.content);
}
+3 -4
View File
@@ -1,8 +1,7 @@
{% load i18n lib_tags %}
<h2>{% trans "Parameters" %} <small>{% trans "Configure Modoboa" %}</small></h2><hr />
<form id="settings_form" method="POST" action="{% url 'core:parameter_save' %}" class="form-horizontal">{% csrf_token %}
{% include "common/parameters.html" %}
<form id="parameters_form" method="POST" action="{% url 'core:parameters' %}" class="form-horizontal">{% csrf_token %}
{% include "parameters/base_form.html" %}
<hr>
<button type="submit" class="btn btn-primary col-xs-offset-4 col-xs-4" id="update" name="update">{% trans 'Save' %}</button>
<button type="submit" class="btn btn-primary col-xs-offset-4 col-xs-4" name="update">{% trans 'Save' %}</button>
</form>
@@ -3,11 +3,11 @@
<hr>
{% if forms %}
<form class="form-horizontal" method="POST" action="{% url 'core:user_preferences' %}"
id="uprefs_form">
id="parameters_form">
{% csrf_token %}
{% include "common/parameters.html" %}
{% include "parameters/base_form.html" %}
<hr>
<button type="submit" class="btn btn-primary col-xs-offset-4 col-xs-4" id="update" name="update">{% trans 'Save' %}</button>
<button type="submit" class="btn btn-primary col-xs-offset-4 col-xs-4" name="update">{% trans 'Save' %}</button>
</form>
{% else %}
<div class="alert alert-info">
+30 -61
View File
@@ -7,9 +7,9 @@ import httmock
from django.core import management
from django.core.urlresolvers import reverse
from django.test import override_settings, TestCase
from django.utils.functional import cached_property
from modoboa.lib import exceptions
from modoboa.lib import parameters
from modoboa.lib.tests import ModoTestCase
from modoboa.lib.tests import NO_LDAP
@@ -58,62 +58,28 @@ class ChangeDefaultAdminTestCase(TestCase):
self.client.login(username="modoadmin", password="password"))
class DashboardTestCase(ModoTestCase):
"""Dashboard tests."""
@classmethod
def setUpTestData(cls):
"""Create some data."""
super(DashboardTestCase, cls).setUpTestData()
cls.dadmin = factories.UserFactory(
username="admin@test.com", groups=("DomainAdmins",)
)
cls.user = factories.UserFactory(
username="user@test.com", groups=("SimpleUsers",)
)
def test_access(self):
"""Load dashboard."""
url = reverse("core:dashboard")
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertIn("Latest news", response.content)
self.client.logout()
self.client.login(username=self.dadmin.username, password="toto")
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertIn("Latest news", response.content)
self.client.logout()
self.client.login(username=self.user.username, password="toto")
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
def test_root_dispatch(self):
"""Check root dispatching."""
url = reverse("core:root")
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.endswith(reverse("core:dashboard")))
@skipIf(NO_LDAP, 'No ldap module installed')
class LDAPTestCaseMixin(object):
"""Set of methods used to test LDAP features."""
@cached_property
def ldapauthbackend(self):
"""Return LDAPAuthBackend instance."""
from modoboa.lib.ldap_utils import LDAPAuthBackend
return LDAPAuthBackend()
def activate_ldap_authentication(self):
"""Modify settings."""
parameters.save_admin(
"AUTHENTICATION_TYPE", "ldap")
parameters.save_admin("LDAP_SERVER_PORT", "3389")
self.set_global_parameters({
"authentication_type": "ldap",
"ldap_server_port": 3389
})
def restore_user_password(self, username, new_password):
"""Restore user password to its initial state."""
from modoboa.lib.ldap_utils import LDAPAuthBackend
backend = LDAPAuthBackend()
for password in ["Toto1234", "test"]:
try:
backend.update_user_password(
self.ldapauthbackend.update_user_password(
username, password, new_password)
except exceptions.InternalError:
pass
@@ -131,16 +97,19 @@ class LDAPTestCaseMixin(object):
def searchbind_mode(self):
"""Apply settings required by the searchbind mode."""
parameters.save_admin("LDAP_AUTH_METHOD", "searchbind")
parameters.save_admin("LDAP_BIND_DN", "cn=admin,dc=example,dc=com")
parameters.save_admin("LDAP_BIND_PASSWORD", "test")
parameters.save_admin("LDAP_SEARCH_BASE", "ou=users,dc=example,dc=com")
self.set_global_parameters({
"ldap_auth_method": "searchbind",
"ldap_bind_dn": "cn=admin,dc=example,dc=com",
"ldap_bind_password": "test",
"ldap_search_base": "ou=users,dc=example,dc=com"
})
def directbind_mode(self):
"""Apply settings required by the directbind mode."""
parameters.save_admin("LDAP_AUTH_METHOD", "directbind")
parameters.save_admin(
"LDAP_USER_DN_TEMPLATE", "cn=%(user)s,ou=users,dc=example,dc=com")
self.set_global_parameters({
"ldap_auth_method": "directbind",
"ldap_user_dn_template": "cn=%(user)s,ou=users,dc=example,dc=com"
})
@override_settings(AUTHENTICATION_BACKENDS=(
@@ -148,7 +117,6 @@ class LDAPTestCaseMixin(object):
'modoboa.lib.authbackends.SimpleBackend',
))
class LDAPAuthenticationTestCase(LDAPTestCaseMixin, ModoTestCase):
"""Validate LDAP authentication scenarios."""
def setUp(self):
@@ -172,11 +140,11 @@ class LDAPAuthenticationTestCase(LDAPTestCaseMixin, ModoTestCase):
username = "testuser@example.com"
self.authenticate(username, "test")
self.check_created_user(username)
self.client.logout()
parameters.save_admin("LDAP_ADMIN_GROUPS", "admins")
parameters.save_admin(
"LDAP_GROUPS_SEARCH_BASE", "ou=groups,dc=example,dc=com")
self.set_global_parameters({
"ldap_admin_groups": "admins",
"ldap_groups_search_base": "ou=groups,dc=example,dc=com"
})
username = "mailadmin@example.com"
self.authenticate(username, "test", False)
self.check_created_user(username, "DomainAdmins")
@@ -194,9 +162,10 @@ class LDAPAuthenticationTestCase(LDAPTestCaseMixin, ModoTestCase):
# 1: must work because usernames of domain admins are not
# always email addresses
parameters.save_admin("LDAP_ADMIN_GROUPS", "admins")
parameters.save_admin(
"LDAP_GROUPS_SEARCH_BASE", "ou=groups,dc=example,dc=com")
self.set_global_parameters({
"ldap_admin_groups": "admins",
"ldap_groups_search_base": "ou=groups,dc=example,dc=com"
})
username = "mailadmin"
self.authenticate(username, "test", False)
self.check_created_user(username, "DomainAdmins", False)
+162
View File
@@ -0,0 +1,162 @@
"""Core views test cases."""
from testfixtures import compare
from django import forms
from django.core.urlresolvers import reverse
from modoboa.lib.tests import ModoTestCase
from modoboa.parameters import forms as param_forms
from modoboa.parameters import tools as param_tools
from .. import factories
from .. import models
class DashboardTestCase(ModoTestCase):
"""Dashboard tests."""
@classmethod
def setUpTestData(cls):
"""Create some data."""
super(DashboardTestCase, cls).setUpTestData()
cls.dadmin = factories.UserFactory(
username="admin@test.com", groups=("DomainAdmins",)
)
cls.user = factories.UserFactory(
username="user@test.com", groups=("SimpleUsers",)
)
def test_access(self):
"""Load dashboard."""
url = reverse("core:dashboard")
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertIn("Latest news", response.content)
self.client.logout()
self.client.login(username=self.dadmin.username, password="toto")
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertIn("Latest news", response.content)
self.client.logout()
self.client.login(username=self.user.username, password="toto")
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
def test_root_dispatch(self):
"""Check root dispatching."""
url = reverse("core:root")
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.endswith(reverse("core:dashboard")))
class SettingsTestCase(ModoTestCase):
"""Settings tests."""
settings_sample = {
"core-check_new_versions": "True",
"core-log_maximum_age": "365",
"core-ldap_auth_method": "searchbind",
"limits-deflt_domain_mailbox_aliases_limit": "0",
"core-send_statistics": "True",
"core-default_top_redirection": "user",
"core-sender_address": "noreply@modoboa.org",
"admin-auto_account_removal": "False",
"core-ldap_server_port": "389",
"core-rounds_number": "70000",
"core-ldap_bind_password": "",
"limits-deflt_domain_domain_aliases_limit": "0",
"core-secret_key": ":?j3]QPWo!.'_c4n",
"relaydomains-master_cf_path": "/etc/postfix/master.cf",
"limits-deflt_domain_domain_admins_limit": "0",
"limits-enable_admin_limits": "True",
"core-ldap_bind_dn": "",
"core-enable_api_communication": "True",
"limits-deflt_user_domain_admins_limit": "0",
"limits-enable_domain_limits": "False",
"csrfmiddlewaretoken": "SGgMVZsA4TPqoiV786TMST6xgOlhAf4F",
"limits-deflt_user_mailboxes_limit": "0",
"core-password_scheme": "sha512crypt",
"core-items_per_page": "30",
"limits-deflt_user_mailbox_aliases_limit": "0",
"limits-deflt_domain_mailboxes_limit": "0",
"core-ldap_is_active_directory": "False",
"core-ldap_group_type": "posixgroup",
"limits-deflt_user_domain_aliases_limit": "0",
"core-top_notifications_check_interval": "30",
"core-ldap_password_attribute": "userPassword",
"admin-auto_create_domain_and_mailbox": "True",
"admin-enable_dnsbl_checks": "True",
"admin-default_domain_quota": "0",
"core-ldap_server_address": "localhost",
"core-authentication_type": "local",
"core-ldap_admin_groups": "",
"core-ldap_groups_search_base": "",
"admin-enable_mx_checks": "True",
"core-ldap_search_base": "",
"admin-valid_mxs": "",
"limits-deflt_user_domains_limit": "0",
"core-ldap_search_filter": "(mail=%(user)s)",
"core-ldap_secured": "False",
"core-ldap_user_dn_template": ""
}
def test_get_settings(self):
"""Test settings display."""
url = reverse("core:parameters")
response = self.ajax_get(url)
for app in ["core", "admin", "limits", "relaydomains"]:
self.assertIn('data-app="{}"'.format(app), response["content"])
def test_save_settings(self):
"""Test settings save."""
url = reverse("core:parameters")
response = self.client.post(url, self.settings_sample, format="json")
self.assertEqual(response.status_code, 200)
self.settings_sample["core-rounds_number"] = ""
response = self.client.post(url, self.settings_sample, format="json")
self.assertEqual(response.status_code, 400)
compare(response.json(), {
"form_errors": {"rounds_number": ["This field is required."]},
"prefix": "core"
})
class UserSettings(param_forms.UserParametersForm):
"""Stupid user settings form."""
app = "core"
test = forms.CharField()
class PreferencesTestCase(ModoTestCase):
"""Test user preferences."""
@classmethod
def setUpClass(cls):
"""Register user form."""
super(PreferencesTestCase, cls).setUpClass()
param_tools.registry.add("user", UserSettings, "Test")
@classmethod
def tearDownClass(cls):
"""Remove user class."""
super(PreferencesTestCase, cls).tearDownClass()
del param_tools.registry._registry["user"]["core"]
def test_get_preferences(self):
"""Test preferences display."""
url = reverse("core:user_preferences")
self.ajax_get(url)
def test_save_preferences(self):
"""Test preferences save."""
url = reverse("core:user_preferences")
response = self.client.post(url, {"core-test": "toto"}, format="json")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), "Preferences saved")
admin = models.User.objects.get(username="admin")
self.assertEqual(admin.parameters.get_value("test"), "toto")
+2 -4
View File
@@ -17,10 +17,8 @@ urlpatterns = patterns(
url(r'^core/$', 'modoboa.core.views.admin.viewsettings',
name="index"),
url(r'^core/parameters/$', 'modoboa.core.views.admin.viewparameters',
name="parameter_listl"),
url(r'^core/parameters/save/$', 'modoboa.core.views.admin.saveparameters',
name="parameter_save"),
url(r'^core/parameters/$', 'modoboa.core.views.admin.parameters',
name="parameters"),
url(r'^core/info/$', 'modoboa.core.views.admin.information',
name="information"),
url(r'^core/logs/$', 'modoboa.core.views.admin.logs', name="log_list"),
+20 -18
View File
@@ -10,11 +10,12 @@ from django.utils.translation import ugettext as _
from modoboa.core.models import Log
from modoboa.core.utils import check_for_updates
from modoboa.lib import events, parameters
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
@@ -27,30 +28,31 @@ def viewsettings(request, tplname='core/settings_header.html'):
@login_required
@user_passes_test(lambda u: u.is_superuser)
def viewparameters(request, tplname='core/parameters.html'):
def parameters(request, tplname="core/parameters.html"):
"""View to display and save global parameters."""
if request.method == "POST":
forms = param_tools.registry.get_forms(
"global", request.POST, localconfig=request.localconfig)
for formdef in forms:
form = formdef["form"]
if form.is_valid():
form.save()
form.to_django_settings()
continue
return render_to_json_response(
{"form_errors": form.errors, "prefix": form.app}, status=400
)
request.localconfig.save()
return render_to_json_response(_("Parameters saved"))
return render_to_json_response({
"left_selection": "parameters",
"content": _render_to_string(request, tplname, {
"forms": parameters.get_admin_forms
"forms": param_tools.registry.get_forms(
"global", localconfig=request.localconfig)
})
})
@login_required
@user_passes_test(lambda u: u.is_superuser)
def saveparameters(request):
for formdef in parameters.get_admin_forms(request.POST):
form = formdef["form"]
if form.is_valid():
form.save()
form.to_django_settings()
continue
return render_to_json_response(
{'form_errors': form.errors, 'prefix': form.app}, status=400
)
return render_to_json_response(_("Parameters saved"))
@login_required
@user_passes_test(lambda u: u.is_superuser)
def information(request, tplname="core/information.html"):
+2 -3
View File
@@ -5,8 +5,6 @@ from django.views import generic
from django.contrib.auth import mixins as auth_mixins
from modoboa.lib import parameters
from ..extensions import exts_pool
@@ -18,7 +16,8 @@ def find_nextlocation(request, user):
nextlocation = request.POST.get("next", None)
if nextlocation is None or nextlocation == "None":
if request.user.role == "SimpleUsers":
topredir = parameters.get_admin("DEFAULT_TOP_REDIRECTION")
topredir = request.localconfig.parameters.get_value(
"default_top_redirection")
if topredir != "user":
infos = exts_pool.get_extension_infos(topredir)
nextlocation = infos["url"] if infos["url"] else infos["name"]
+9 -4
View File
@@ -8,11 +8,13 @@ from django.contrib.auth.decorators import login_required, user_passes_test
from rest_framework.authtoken.models import Token
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
@@ -59,7 +61,9 @@ def profile(request, tplname='core/user_profile.html'):
@login_required
def preferences(request):
if request.method == "POST":
for formdef in parameters.get_user_forms(request.user, request.POST):
forms = param_tools.registry.get_forms(
"user", request.POST, user=request.user)
for formdef in forms:
form = formdef["form"]
if form.is_valid():
form.save()
@@ -67,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")
})
})
+8 -9
View File
@@ -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,8 +35,10 @@ 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)
self.global_params = dict(
param_tools.get_global_parameters("core"))
def get_or_create_user(self, username, ldap_user):
"""
@@ -46,12 +47,11 @@ try:
the user. ldap_user.dn is the user's DN and
ldap_user.attrs contains all of their LDAP attributes.
"""
group = 'SimpleUsers'
admin_groups = parameters \
.get_admin('LDAP_ADMIN_GROUPS', app='core').split(';')
group = "SimpleUsers"
admin_groups = self.global_params["ldap_admin_groups"].split(";")
for grp in admin_groups:
if grp.strip() in ldap_user.group_names:
group = 'DomainAdmins'
group = "DomainAdmins"
break
if group == 'SimpleUsers':
lpart, domain = split_mailbox(username)
@@ -75,8 +75,7 @@ try:
return user
def authenticate(self, username, password):
auth_type = parameters.get_admin("AUTHENTICATION_TYPE", app="core")
if auth_type == "ldap":
if self.global_params["authentication_type"] == "ldap":
return super(LDAPBackend, self).authenticate(
username, password)
return None
+4 -3
View File
@@ -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)
+27 -13
View File
@@ -6,7 +6,7 @@ import abc
from collections import OrderedDict
import re
from django.forms import ChoiceField
from django.forms import TypedChoiceField
from django.forms.fields import Field
from django.forms.widgets import RadioSelect
from django.forms.widgets import RadioChoiceInput
@@ -232,9 +232,8 @@ class DynamicForm(object):
class TabForms(object):
"""
Simple forms container
Simple forms container.
This class tries to encapsulate multiple forms that will be
displayed using tabs. It is different from a classical formset
@@ -371,19 +370,23 @@ class TabForms(object):
class CustomRadioInput(RadioChoiceInput):
"""Custom radio input."""
def __unicode__(self):
if 'id' in self.attrs:
label_for = ' for="%s"' % self.attrs['id']
if "id" in self.attrs:
label_for = ' for="%s"' % self.attrs["id"]
else:
label_for = ''
label_for = ""
choice_label = conditional_escape(force_unicode(self.choice_label))
return mark_safe(
u'<label class="radio-inline" %s>%s %s</label>'
u"<label class='radio-inline' %s>%s %s</label>"
% (label_for, self.tag(), choice_label)
)
class InlineRadioRenderer(RadioSelect.renderer):
"""Custom inline radio renderer."""
def __iter__(self):
for i, choice in enumerate(self.choices):
yield CustomRadioInput(
@@ -392,24 +395,35 @@ class InlineRadioRenderer(RadioSelect.renderer):
def render(self):
return mark_safe(
u'\n'.join([u'%s\n' % force_unicode(w) for w in self])
u"\n".join([u"%s\n" % force_unicode(w) for w in self])
)
class InlineRadioSelect(RadioSelect):
"""Custom inline radio widget."""
renderer = InlineRadioRenderer
class SeparatorField(Field):
"""Custom field to represent a separator."""
def __init__(self, *args, **kwargs):
kwargs["required"] = False
super(SeparatorField, self).__init__(*args, **kwargs)
class YesNoField(ChoiceField):
class YesNoField(TypedChoiceField):
"""A yes/no form field."""
def __init__(self, *args, **kwargs):
kwargs["choices"] = [
("yes", ugettext_lazy("Yes")), ("no", ugettext_lazy("No"))
]
kwargs["widget"] = InlineRadioSelect
"""Constructor."""
kwargs.update({
"choices": (
(True, ugettext_lazy("Yes")),
(False, ugettext_lazy("No"))
),
"widget": InlineRadioSelect,
"coerce": lambda x: x == "True"
})
super(YesNoField, self).__init__(*args, **kwargs)
+10 -9
View File
@@ -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,16 @@ 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.global_params = dict(param_tools.get_global_parameters("core"))
self.server_uri = self._setting(
"AUTH_LDAP_SERVER_URI", "ldap://localhost"
)
@@ -74,7 +75,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 = self.global_params["ldap_auth_method"]
if mode == "searchbind":
bind_dn = self._setting("AUTH_LDAP_BIND_DN", "")
bind_pwd = self._setting("AUTH_LDAP_BIND_PASSWORD", "")
@@ -86,8 +87,8 @@ 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")
sbase = self.global_params["ldap_search_base"]
sfilter = self.global_params["ldap_search_filter"]
sfilter = sfilter % {"user": user}
res = self.conn.search_s(sbase, ldap.SCOPE_SUBTREE, sfilter)
try:
@@ -102,8 +103,8 @@ class LDAPAuthBackend(object):
:param clearpassword: the clear password
:return: the encrypted password
"""
scheme = parameters.get_admin("PASSWORD_SCHEME", app="core")
hasher = get_password_hasher(scheme.upper())('ldap')
scheme = self.global_params["password_scheme"]
hasher = get_password_hasher(scheme.upper())("ldap")
return hasher.encrypt(clearpassword)
def update_user_password(self, user, password, newpassword):
+3 -2
View File
@@ -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:
-371
View File
@@ -1,371 +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.lib import events
from modoboa.lib.exceptions import ModoboaException
from modoboa.lib.sysutils import guess_extension_name
from . import db_utils
_params = {'A': {}, 'U': {}}
class NotDefined(ModoboaException):
http_code = 404
def __init__(self, app, name):
self.app = app
self.name = name
def __str__(self):
return "Application '%s' and/or parameter '%s' not defined" \
% (self.app, self.name)
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):
if self.app is None:
raise NotImplementedError
kwargs["prefix"] = self.app
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:
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()
def save(self):
raise NotImplementedError
class AdminParametersForm(GenericParametersForm):
def _load_initial_values(self):
from .models import Parameter
if not db_utils.db_table_exists("lib_parameter"):
return
names = [
"%s.%s" % (self.app, name.upper()) for name in self.fields.keys()
]
for p in Parameter.objects.filter(name__in=names):
self.fields[p.shortname].initial = self._decode_value(p.value)
def save(self):
from .models import Parameter
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 = Parameter.objects.get(name=fullname)
except Parameter.DoesNotExist:
p = Parameter()
p.name = fullname
self._save_parameter(p, name, value)
def to_django_settings(self):
pass
def get_current_values(self):
values = {}
for key in self.fields.keys():
try:
values[key] = get_admin(key.upper(), app=self.app)
except 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)
@staticmethod
def has_access(user):
return True
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
"""
from modoboa.lib.form_utils import SeparatorField
if issubclass(formclass, AdminParametersForm):
level = 'A'
elif issubclass(formclass, UserParametersForm):
level = 'U'
else:
raise RuntimeError("Unknown parameter class")
_params[level][formclass.app] = {
"label": label, "form": formclass, "defaults": {}
}
form = formclass()
for name, field in form.fields.items():
if type(field) is SeparatorField:
continue
_params[level][formclass.app]["defaults"][name.upper()] = field.initial
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
"""
from .models import Parameter
if app is None:
app = guess_extension_name()
try:
__is_defined(app, "A", name)
except NotDefined:
if raise_error:
raise
return None
try:
p = Parameter.objects.get(name="%s.%s" % (app, name))
except Parameter.DoesNotExist:
return _params["A"][app]["defaults"][name]
return p.value.decode("unicode_escape").replace('\\r\\n', '\n')
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_sorted_apps(level, first="core"):
"""Retrieve the sorted list of all registerd applications.
:param str level: application level
:param str first: force the first item of the result
:rtype: list
"""
sorted_apps = []
if first in _params[level]:
sorted_apps.append(first)
sorted_apps += sorted(
[app for app in _params[level].keys() if app != first],
key=lambda app: _params[level][app]["label"]
)
return sorted_apps
def get_admin_forms(*args, **kwargs):
"""Get all admin level forms.
Generates an instance of each declared form.
"""
for app in get_sorted_apps('A'):
formdef = _params['A'][app]
yield {"label": formdef["label"],
"form": formdef["form"](*args, **kwargs)}
def get_user_forms(user, *args, **kwargs):
"""Return an instance of each user-level forms."""
kwargs["user"] = user
sorted_apps = get_sorted_apps('U', first="general")
result = []
for app in sorted_apps:
formdef = _params["U"][app]
if not formdef["form"].has_access(user):
continue
result.append({
"label": formdef["label"],
"form": formdef["form"](*args, **kwargs)})
return result
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()
+30 -78
View File
@@ -3,16 +3,16 @@
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
try:
import ldap # NOQA
NO_LDAP = False
@@ -20,16 +20,41 @@ except ImportError:
NO_LDAP = True
class ModoTestCase(TestCase):
class ParametersMixin(object):
"""Add tools to manage parameters."""
@classmethod
def setUpTestData(cls):
"""Set LocalConfig instance."""
super(ParametersMixin, cls).setUpTestData()
cls.localconfig = core_models.LocalConfig.objects.first()
def set_global_parameter(self, name, value, app=None):
"""Set global parameter for the given app."""
if app is None:
app = sysutils.guess_extension_name()
self.localconfig.parameters.set_value(name, value, app=app)
self.localconfig.save()
def set_global_parameters(self, parameters, app=None):
"""Set/update global parameters for the given app."""
if app is None:
app = sysutils.guess_extension_name()
self.localconfig.parameters.set_values(parameters, app=app)
self.localconfig.save()
class ModoTestCase(ParametersMixin, TestCase):
"""All test cases must inherit from this one."""
@classmethod
def setUpTestData(cls):
"""Create a default user."""
super(ModoTestCase, cls).setUpTestData()
management.call_command("load_initial_data")
def setUp(self, username="admin", password="password"):
"""Initiate test context."""
self.assertEqual(
self.client.login(username=username, password=password), True)
@@ -54,13 +79,13 @@ class ModoTestCase(TestCase):
return self.ajax_request('get', *args, **kwargs)
class ModoAPITestCase(APITestCase):
class ModoAPITestCase(ParametersMixin, APITestCase):
"""All test cases must inherit from this one."""
@classmethod
def setUpTestData(cls):
"""Create a default user."""
super(ModoAPITestCase, cls).setUpTestData()
management.call_command("load_initial_data")
cls.token = Token.objects.create(
user=core_models.User.objects.get(username="admin"))
@@ -69,76 +94,3 @@ class ModoAPITestCase(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")
+13 -17
View File
@@ -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"
@@ -16,7 +16,7 @@ class ParametersForm(AdminParametersForm):
enable_admin_limits = YesNoField(
label=_("Enable per-admin limits"),
initial="yes",
initial=True,
help_text=_("Enable or disable per-admin limits")
)
@@ -77,7 +77,7 @@ class ParametersForm(AdminParametersForm):
enable_domain_limits = YesNoField(
label=_("Enable per-domain limits"),
initial="no",
initial=False,
help_text=_("Enable or disable per-domain limits")
)
@@ -125,17 +125,13 @@ class ParametersForm(AdminParametersForm):
)
visibility_rules = {
"deflt_user_domains_limit": "enable_admin_limits=yes",
"deflt_user_domain_aliases_limit": "enable_admin_limits=yes",
"deflt_user_mailboxes_limit": "enable_admin_limits=yes",
"deflt_user_mailbox_aliases_limit": "enable_admin_limits=yes",
"deflt_user_domain_admins_limit": "enable_admin_limits=yes",
"deflt_domain_mailboxes_limit": "enable_domain_limits=yes",
"deflt_domain_mailbox_aliases_limit": "enable_domain_limits=yes",
"deflt_domain_domain_aliases_limit": "enable_domain_limits=yes",
"deflt_domain_domain_admins_limit": "enable_domain_limits=yes",
"deflt_user_domains_limit": "enable_admin_limits=True",
"deflt_user_domain_aliases_limit": "enable_admin_limits=True",
"deflt_user_mailboxes_limit": "enable_admin_limits=True",
"deflt_user_mailbox_aliases_limit": "enable_admin_limits=True",
"deflt_user_domain_admins_limit": "enable_admin_limits=True",
"deflt_domain_mailboxes_limit": "enable_domain_limits=True",
"deflt_domain_mailbox_aliases_limit": "enable_domain_limits=True",
"deflt_domain_domain_aliases_limit": "enable_domain_limits=True",
"deflt_domain_domain_admins_limit": "enable_domain_limits=True",
}
def __init__(self, *args, **kwargs):
super(AdminParametersForm, self).__init__(*args, **kwargs)
self._load_extra_parameters('A')
+5 -5
View File
@@ -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
+5 -7
View File
@@ -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 = (
parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no" or
not param_tools.get_global_parameter("enable_admin_limits") or
role != "DomainAdmins")
if condition:
return [True]
+8 -7
View File
@@ -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 = (
parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no" 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 parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no":
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 parameters.get_admin("ENABLE_DOMAIN_LIMITS") == "no":
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 parameters.get_admin("ENABLE_DOMAIN_LIMITS") == "no":
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 = (
parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no" 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 = (
parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no" or
not param_tools.get_global_parameter("enable_admin_limits") or
caller not in ["domains", "identities"] or
user.role in ["SuperAdmins", "SimpleUsers"]
)
+9 -10
View File
@@ -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 parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no":
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 parameters.get_admin("ENABLE_DOMAIN_LIMITS") == "no":
if not param_tools.get_global_parameter("enable_domain_limits"):
return
limit = context.domainobjectlimit_set.get(name=object_type)
else:
@@ -44,15 +44,14 @@ def create_user_limits(sender, instance, **kwargs):
return
request = lib_signals.get_request()
creator = request.user if request else None
global_params = dict(param_tools.get_global_parameters("limits"))
for name, definition in utils.get_user_limit_templates():
ct = ContentType.objects.get_by_natural_key(
*definition["content_type"].split("."))
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 = global_params["deflt_user_{0}_limit".format(name)]
models.UserObjectLimit.objects.create(
user=instance, name=name, content_type=ct, max_value=max_value)
@@ -62,9 +61,9 @@ def create_domain_limits(sender, instance, **kwargs):
"""Create limits for new domain."""
if not kwargs.get("created"):
return
global_params = dict(param_tools.get_global_parameters("limits"))
for name, definition in utils.get_domain_limit_templates():
max_value = int(
parameters.get_admin("DEFLT_DOMAIN_{}_LIMIT".format(name.upper())))
max_value = global_params["deflt_domain_{0}_limit".format(name)]
models.DomainObjectLimit.objects.create(
domain=instance, name=name, max_value=max_value)
@@ -72,7 +71,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 parameters.get_admin("ENABLE_DOMAIN_LIMITS") == "no":
if not param_tools.get_global_parameter("enable_domain_limits"):
return []
return [{
"column": "right",
@@ -87,7 +86,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") == "yes" and
param_tools.get_global_parameter("enable_admin_limits") and
account.role in ["DomainAdmins", "Resellers"]
)
if not condition:
+8 -6
View File
@@ -9,7 +9,6 @@ from modoboa.admin.factories import populate_database
from modoboa.admin.models import Domain
from modoboa.core import factories as core_factories
from modoboa.core.models import User
from modoboa.lib import parameters
from modoboa.lib import tests as lib_tests
from .. import utils
@@ -23,8 +22,9 @@ class APIAdminLimitsTestCase(lib_tests.ModoAPITestCase):
"""Create test data."""
super(APIAdminLimitsTestCase, cls).setUpTestData()
for name, tpl in utils.get_user_limit_templates():
parameters.save_admin(
"DEFLT_USER_{}_LIMIT".format(name.upper()), 2)
cls.localconfig.parameters.set_value(
"deflt_user_{0}_limit".format(name), 2)
cls.localconfig.save()
populate_database()
cls.user = User.objects.get(username="admin@test.com")
cls.da_token = Token.objects.create(user=cls.user)
@@ -181,10 +181,12 @@ class APIDomainLimitsTestCase(lib_tests.ModoAPITestCase):
def setUpTestData(cls):
"""Create test data."""
super(APIDomainLimitsTestCase, cls).setUpTestData()
parameters.save_admin("ENABLE_DOMAIN_LIMITS", "yes")
cls.localconfig.parameters.set_value(
"enable_domain_limits", True)
for name, tpl in utils.get_domain_limit_templates():
parameters.save_admin(
"DEFLT_DOMAIN_{}_LIMIT".format(name.upper()), 2)
cls.localconfig.parameters.set_value(
"deflt_domain_{0}_limit".format(name), 2)
cls.localconfig.save()
populate_database()
def test_mailboxes_limit(self):
+7 -5
View File
@@ -7,7 +7,6 @@ from modoboa.admin import factories as admin_factories
from modoboa.admin.models import Domain
from modoboa.core import factories as core_factories
from modoboa.core.models import User
from modoboa.lib import parameters
from modoboa.lib import tests as lib_tests
from .. import utils
@@ -20,11 +19,14 @@ class DomainLimitsTestCase(lib_tests.ModoTestCase):
def setUpTestData(cls):
"""Create test data."""
super(DomainLimitsTestCase, cls).setUpTestData()
parameters.save_admin("ENABLE_ADMIN_LIMITS", "no")
parameters.save_admin("ENABLE_DOMAIN_LIMITS", "yes")
cls.localconfig.parameters.set_values({
"enable_admin_limits": False,
"enable_domain_limits": True
})
for name, tpl in utils.get_domain_limit_templates():
parameters.save_admin(
"DEFLT_DOMAIN_{}_LIMIT".format(name.upper()), 2)
cls.localconfig.parameters.set_value(
"deflt_domain_{0}_limit".format(name), 2)
cls.localconfig.save()
admin_factories.populate_database()
def test_set_limits(self):
+12 -7
View File
@@ -6,7 +6,6 @@ from modoboa.admin import factories as admin_factories
from modoboa.admin import models as admin_models
from modoboa.core import factories as core_factories
from modoboa.core import models as core_models
from modoboa.lib import parameters
from modoboa.lib.tests import ModoTestCase
from .. import utils
@@ -135,9 +134,11 @@ class UserLimitImportTestCase(LimitImportTestCase):
@classmethod
def setUpTestData(cls):
"""Create test data."""
localconfig = core_models.LocalConfig.objects.first()
for name, tpl in utils.get_user_limit_templates():
parameters.save_admin(
"DEFLT_USER_{}_LIMIT".format(name.upper()), 2)
localconfig.parameters.set_value(
"deflt_user_{0}_limit".format(name), 2)
localconfig.save()
super(UserLimitImportTestCase, cls).setUpTestData()
def test_domains_import(self):
@@ -192,11 +193,15 @@ class DomainLimitImportTestCase(LimitImportTestCase):
@classmethod
def setUpTestData(cls):
"""Create test data."""
parameters.save_admin("ENABLE_DOMAIN_LIMITS", "yes")
parameters.save_admin("ENABLE_ADMIN_LIMITS", "no")
localconfig = core_models.LocalConfig.objects.first()
localconfig.parameters.set_values({
"enable_domain_limits": True,
"enable_admin_limits": False
})
for name, tpl in utils.get_domain_limit_templates():
parameters.save_admin(
"DEFLT_DOMAIN_{}_LIMIT".format(name.upper()), 2)
localconfig.parameters.set_value(
"deflt_domain_{0}_limit".format(name), 2)
localconfig.save()
super(DomainLimitImportTestCase, cls).setUpTestData()
mb = admin_factories.MailboxFactory(
user__username="user@test4.com",
+14 -12
View File
@@ -7,7 +7,6 @@ from modoboa.admin.factories import populate_database
from modoboa.admin.models import Alias, Domain
from modoboa.core.factories import UserFactory
from modoboa.core.models import User
from modoboa.lib import parameters
from modoboa.lib import tests as lib_tests
from .. import utils
@@ -48,13 +47,14 @@ class ResourceTestCase(lib_tests.ModoTestCase):
def setUpTestData(cls):
"""Custom setUpTestData method."""
super(ResourceTestCase, cls).setUpTestData()
parameters.save_admin("ENABLE_ADMIN_LIMITS", "yes")
parameters.save_admin("ENABLE_DOMAIN_LIMITS", "no")
cls.localconfig.parameters.set_values({
"enable_admin_limits": True,
"enable_domain_limits": False
})
for name, tpl in utils.get_user_limit_templates():
parameters.save_admin(
"DEFLT_USER_{0}_LIMIT".format(name.upper()), 2,
app="limits"
)
cls.localconfig.parameters.set_value(
"deflt_user_{0}_limit".format(name), 2)
cls.localconfig.save()
populate_database()
def _create_account(self, username, role='SimpleUsers', status=200):
@@ -78,13 +78,15 @@ class ResourceTestCase(lib_tests.ModoTestCase):
def _create_domain(self, name, status=200, withtpl=False):
values = {
"name": name, "quota": 100, "create_dom_admin": "no",
"create_aliases": "no", "stepid": 'step3', "type": "domain"
"name": name, "quota": 100, "create_dom_admin": False,
"create_aliases": False, "stepid": "step3", "type": "domain"
}
if withtpl:
values['create_dom_admin'] = 'yes'
values['dom_admin_username'] = 'admin'
values['create_aliases'] = 'yes'
values.update({
"create_dom_admin": True,
"dom_admin_username": "admin",
"create_aliases": True
})
response = self.ajax_post(
reverse("admin:domain_add"), values, status
)
View File
+7
View File
@@ -0,0 +1,7 @@
from __future__ import unicode_literals
from django.apps import AppConfig
class ParametersConfig(AppConfig):
name = 'parameters'
+107
View File
@@ -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)
@@ -1,7 +1,7 @@
{% load form_tags %}
<div class="tabbable">
<ul class="nav nav-tabs">
{% for form in forms %}<li{% if forloop.first %} class="active"{% endif %}><a href="#{{ forloop.counter }}" data-toggle="tab">{{ form.label }}</a></li>
{% for form in forms %}<li{% if forloop.first %} class="active"{% endif %}><a href="#{{ forloop.counter }}" data-app="{{ form.app }}" data-toggle="tab">{{ form.label }}</a></li>
{% endfor %}
</ul>
<div class="tab-content">{% for form in forms %}
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+203
View File
@@ -0,0 +1,203 @@
"""Parameters management."""
import copy
from modoboa.lib import exceptions
from modoboa.lib import form_utils
from modoboa.lib import signals
from modoboa.lib.sysutils import guess_extension_name
class NotDefined(exceptions.ModoboaException):
"""Custom exception for undefined parameters."""
http_code = 404
def __init__(self, app, name=None):
self.app = app
self.name = name
def __str__(self):
if self.name is None:
return "Application {} not registered".format(self.app)
return "Parameter {} not defined for app {}".format(
self.name, self.app)
class Registry(object):
"""A registry for parameters."""
def __init__(self):
"""Constructor."""
self._registry = {
"global": {},
"user": {}
}
def add(self, level, formclass, label):
"""Add a new class containing parameters."""
self._registry[level][formclass.app] = {
"label": label, "formclass": formclass, "defaults": {}
}
def _load_default_values(self, level):
"""Load default values."""
for app, data in self._registry[level].items():
form = data["formclass"](load_values_from_db=False)
for name, field in form.fields.items():
if type(field) is form_utils.SeparatorField:
continue
data["defaults"][name] = field.initial
def get_forms(self, level, *args, **kwargs):
"""Return form instances for all app of the given level."""
sorted_apps = []
first_app = kwargs.pop("first_app", "core")
if first_app in self._registry[level]:
sorted_apps.append(first_app)
sorted_apps += sorted(
[a for a in self._registry[level].keys() if a != first_app],
key=lambda a: self._registry[level][a]["label"]
)
result = []
for app in sorted_apps:
data = self._registry[level][app]
if not data["formclass"].has_access(**kwargs):
continue
result.append({
"app": app,
"label": data["label"],
"form": data["formclass"](*args, **kwargs)
})
return result
def exists(self, level, app, parameter=None):
"""Check if parameter exists."""
parameters = self._registry[level]
result = app in parameters
if parameter:
result = result and parameter in parameters[app]["defaults"]
return result
def get_default(self, level, app, parameter):
"""Retrieve default value for parameter."""
if app not in self._registry[level]:
raise NotDefined(app)
if parameter not in self._registry[level][app]["defaults"]:
raise NotDefined(app, parameter)
return self._registry[level][app]["defaults"][parameter]
def get_defaults(self, level, app):
"""Retrieve default values for application."""
if app not in self._registry[level]:
raise NotDefined(app)
return self._registry[level][app]["defaults"]
registry = Registry()
class Manager(object):
"""A manager for parameters."""
def __init__(self, level, parameters):
"""Constructor."""
self._level = level
self._parameters = parameters
registry._load_default_values(level)
def get_value(self, parameter, app=None, raise_exception=True):
"""Return the value associated to the specified parameter."""
if app is None:
app = guess_extension_name()
# Compat. with the old way...
parameter = parameter.lower()
try:
default = registry.get_default(self._level, app, parameter)
except NotDefined:
if raise_exception:
raise
return None
if app in self._parameters:
return self._parameters[app][parameter]
return default
def get_values(self, app=None):
"""Return all values for the given level/app."""
if app is None:
app = guess_extension_name()
values = registry.get_defaults(self._level, app)
for parameter, value in values.iteritems():
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."""
if app is None:
app = guess_extension_name()
if not registry.exists(self._level, app, parameter):
raise NotDefined(app, parameter)
if app not in self._parameters:
self._parameters[app] = copy.deepcopy(
registry.get_defaults(self._level, app))
self._parameters[app][parameter] = value
def set_values(self, values, app=None):
"""Set/update values for the given app."""
if app is None:
app = guess_extension_name()
if not registry.exists(self._level, app):
raise NotDefined(app)
if app not in self._parameters:
self._parameters[app] = copy.deepcopy(
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()
+3 -6
View File
@@ -1,16 +1,13 @@
"""
Relay domains settings definition.
"""
"""Relay domains settings definition."""
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"
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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'):
+10 -9
View File
@@ -6,7 +6,6 @@ from django.test import TestCase
from modoboa.admin import factories as admin_factories
from modoboa.admin import models as admin_models
from modoboa.core.factories import UserFactory
from modoboa.lib import parameters
from modoboa.lib.tests import ModoTestCase
from modoboa.lib.test_utils import MapFilesTestCaseMixin
from modoboa.limits import utils as limits_utils
@@ -20,7 +19,7 @@ class Operations(object):
def _create_relay_domain(self, name, status=200):
srv, created = Service.objects.get_or_create(name='dummy')
values = {
"name": name, "create_dom_admin": "no", "type": "relaydomain",
"name": name, "create_dom_admin": False, "type": "relaydomain",
"target_host": "external.host.tld", "service": srv.id,
"enabled": True, "stepid": "step3"
}
@@ -213,15 +212,18 @@ class RelayDomainsTestCase(ModoTestCase, Operations):
"enabled": True
}
self.ajax_post(reverse("admin:alias_add"), values)
alias = admin_models.Alias.objects.get(address="alias@relaydomain.tld")
self.assertTrue(
admin_models.Alias.objects.filter(
address="alias@relaydomain.tld").exists())
values = {
"address": "alias2@relaydomain.tld",
"recipients": "local@relaydomain.tld",
"enabled": True
}
self.ajax_post(reverse("admin:alias_add"), values)
alias = admin_models.Alias.objects.get(
address="alias2@relaydomain.tld")
self.assertTrue(
admin_models.Alias.objects.filter(
address="alias2@relaydomain.tld").exists())
class LimitsTestCase(ModoTestCase, Operations):
@@ -230,11 +232,10 @@ class LimitsTestCase(ModoTestCase, Operations):
def setUpTestData(cls):
"""Create test data."""
super(LimitsTestCase, cls).setUpTestData()
for name, tpl in limits_utils.get_user_limit_templates():
parameters.save_admin(
"DEFLT_USER_{0}_LIMIT".format(name.upper()), 2, app="limits"
)
cls.localconfig.parameters.set_value(
"deflt_user_{0}_limit".format(name), 2, app="limits")
cls.localconfig.save()
cls.user = UserFactory.create(
username='reseller', groups=('Resellers',)
)
+14
View File
@@ -3,6 +3,9 @@
*
* @constructor
*/
/* global Listing */
var TwocolsNav = function(options) {
Listing.call(this, options);
};
@@ -33,6 +36,17 @@ TwocolsNav.prototype = {
success_cb: $.proxy(this.save_cb, this)
});
}, this));
$(document).on('submit', '#parameters_form', function(evt) {
simple_ajax_form_post(evt, {
formid: $(this).attr('id'),
modal: false,
reload_on_success: false,
success_cb: $.proxy(this.save_cb, this),
error_cb: function(data) {
$('a[data-app="' + data.prefix + '"]').tab('show');
}
});
});
},
update_content: function(data) {
+2
View File
@@ -60,6 +60,7 @@ MODOBOA_APPS = (
'modoboa.admin',
'modoboa.limits',
'modoboa.relaydomains',
'modoboa.parameters',
)
INSTALLED_APPS += MODOBOA_APPS
@@ -75,6 +76,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',