diff --git a/doc/configuration.rst b/doc/configuration.rst
index dbaed6d09..628261179 100644
--- a/doc/configuration.rst
+++ b/doc/configuration.rst
@@ -103,6 +103,20 @@ The *admin* application exposes several parameters, they are presented below:
| | | | |
| | | | |
+--------------------+---------------------+-------------------------------+--------------------+
+|DKIM keys storage |Admin |Path to a directory where DKIM | |
+|directory | | generated keys will be stored | |
+| | | | |
+| | | | |
+| | | | |
+| | | | |
++--------------------+---------------------+-------------------------------+--------------------+
+|Default DKIM key |Admin |The default size (in bits) for |2048 |
+|length | |new keys | |
+| | | | |
+| | | | |
+| | | | |
+| | | | |
++--------------------+---------------------+-------------------------------+--------------------+
|Handle mailboxes on |Admin |Rename or remove |no |
|filesystem | |mailboxes on the | |
| | |filesystem when they | |
diff --git a/doc/manual_installation/modoboa.rst b/doc/manual_installation/modoboa.rst
index d2842360b..5387a851b 100644
--- a/doc/manual_installation/modoboa.rst
+++ b/doc/manual_installation/modoboa.rst
@@ -190,5 +190,8 @@ following content inside::
# Public API communication
0 * * * * root $PYTHON $INSTANCE/manage.py communicate_with_public_api
+ # Generate DKIM keys (they will belong to the user running this job)
+ * * * * * root $PYTHON $INSTANCE/manage.py modo manage_dkim_keys
+
Now you can continue to the :ref:`webserver` section.
diff --git a/modoboa/admin/app_settings.py b/modoboa/admin/app_settings.py
index 95004aeab..a72dd7fff 100644
--- a/modoboa/admin/app_settings.py
+++ b/modoboa/admin/app_settings.py
@@ -13,6 +13,8 @@ from modoboa.lib.form_utils import YesNoField, SeparatorField
from modoboa.lib.sysutils import exec_cmd
from modoboa.parameters import forms as param_forms
+from . import constants
+
class AdminParametersForm(param_forms.AdminParametersForm):
app = "admin"
@@ -55,6 +57,26 @@ class AdminParametersForm(param_forms.AdminParametersForm):
)
)
+ dkim_keys_storage_dir = forms.CharField(
+ label=ugettext_lazy("DKIM keys storage directory"),
+ initial="",
+ help_text=ugettext_lazy(
+ "Absolute path of the directory where DKIM private keys will "
+ "be stored. Make sure this directory belongs to root user "
+ "and is not readable by the outside world."
+ ),
+ required=False
+ )
+
+ dkim_default_key_length = forms.ChoiceField(
+ label=ugettext_lazy("Default DKIM key length"),
+ initial=2048,
+ choices=constants.DKIM_KEY_LENGTHS,
+ help_text=ugettext_lazy(
+ "Default length in bits for newly generated DKIM keys."
+ )
+ )
+
mbsep = SeparatorField(label=ugettext_lazy("Mailboxes"))
handle_mailboxes = YesNoField(
@@ -173,6 +195,22 @@ class AdminParametersForm(param_forms.AdminParametersForm):
)
return self.cleaned_data["default_mailbox_quota"]
+ def clean_dkim_keys_storage_dir(self):
+ """Check that directory exists."""
+ storage_dir = self.cleaned_data.get("dkim_keys_storage_dir", "")
+ if storage_dir:
+ if not os.path.isdir(storage_dir):
+ raise forms.ValidationError(
+ ugettext_lazy("Directory not found.")
+ )
+ code, output = exec_cmd("which openssl")
+ if code:
+ raise forms.ValidationError(
+ ugettext_lazy(
+ "openssl not found, please make sure it is installed.")
+ )
+ return storage_dir
+
def clean(self):
"""Check MX options."""
cleaned_data = super(AdminParametersForm, self).clean()
diff --git a/modoboa/admin/constants.py b/modoboa/admin/constants.py
index 42fdc1277..372f1ef20 100644
--- a/modoboa/admin/constants.py
+++ b/modoboa/admin/constants.py
@@ -99,3 +99,9 @@ DOMAIN_TYPES = [
("domain", _("Domain")),
("relaydomain", _("Relay domain")),
]
+
+DKIM_KEY_LENGTHS = [
+ (1024, "1024"),
+ (2048, "2048"),
+ (4096, "4096"),
+]
diff --git a/modoboa/admin/forms/domain.py b/modoboa/admin/forms/domain.py
index 558b6e89f..ddd4bf070 100644
--- a/modoboa/admin/forms/domain.py
+++ b/modoboa/admin/forms/domain.py
@@ -43,12 +43,16 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
"press ENTER to add a new input."
)
)
+ dkim_key_selector = forms.CharField(
+ label=ugettext_lazy("Key selector"), required=False)
class Meta:
model = Domain
fields = (
"name", "type", "quota", "default_mailbox_quota", "aliases",
- "enabled", "enable_dns_checks")
+ "enabled", "enable_dns_checks", "enable_dkim",
+ "dkim_key_selector", "dkim_key_length"
+ )
def __init__(self, user, *args, **kwargs):
self.oldname = None
@@ -93,6 +97,17 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
return name
+ def clean_enable_dkim(self):
+ """Check prerequisites."""
+ enabled = self.cleaned_data.get("enable_dkim")
+ if not enabled:
+ return enabled
+ storage_dir = param_tools.get_global_parameter("dkim_keys_storage_dir")
+ if not storage_dir:
+ raise forms.ValidationError(
+ _("DKIM keys storage directory not configured"))
+ return enabled
+
def clean(self):
"""Custom fields validation.
@@ -123,6 +138,10 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
"default_mailbox_quota"]
if default_mailbox_quota == 0:
self.add_error("default_mailbox_quota", msg)
+ if self.cleaned_data["enable_dkim"]:
+ if not self.cleaned_data.get("dkim_key_selector"):
+ self.add_error(
+ "dkim_key_selector", _("This field is required."))
self.aliases = []
copied_data = cleaned_data.copy()
for k in copied_data.keys():
diff --git a/modoboa/admin/management/commands/modo.py b/modoboa/admin/management/commands/modo.py
index 5088ace23..d395c4c19 100644
--- a/modoboa/admin/management/commands/modo.py
+++ b/modoboa/admin/management/commands/modo.py
@@ -7,6 +7,7 @@ from subcommand.base import SubcommandCommand
from .subcommands._mx import CheckMXRecords
from .subcommands._export import ExportCommand
from .subcommands._import import ImportCommand
+from .subcommands._manage_dkim_keys import ManageDKIMKeys
from .subcommands._repair import Repair
@@ -22,5 +23,6 @@ class Command(SubcommandCommand):
"export": ExportCommand,
"import": ImportCommand,
"check_mx": CheckMXRecords,
+ "manage_dkim_keys": ManageDKIMKeys,
"repair": Repair,
}
diff --git a/modoboa/admin/management/commands/subcommands/_manage_dkim_keys.py b/modoboa/admin/management/commands/subcommands/_manage_dkim_keys.py
new file mode 100644
index 000000000..bae55519c
--- /dev/null
+++ b/modoboa/admin/management/commands/subcommands/_manage_dkim_keys.py
@@ -0,0 +1,52 @@
+"""Management command to create DKIM keys."""
+
+from __future__ import print_function, unicode_literals
+
+import os
+
+from django.core.management.base import BaseCommand
+from django.utils.encoding import smart_text
+
+from modoboa.lib import sysutils
+from modoboa.parameters import tools as param_tools
+
+from .... import models
+
+
+class ManageDKIMKeys(BaseCommand):
+ """Command class."""
+
+ def create_new_dkim_key(self, domain):
+ """Create a new DKIM key."""
+ storage_dir = param_tools.get_global_parameter("dkim_keys_storage_dir")
+ pkey_path = os.path.join(storage_dir, "{}.pem".format(domain.name))
+ key_size = (
+ domain.dkim_key_length if domain.dkim_key_length
+ else self.default_key_length)
+ code, output = sysutils.exec_cmd(
+ "openssl genrsa -out {} {}".format(pkey_path, key_size))
+ if code:
+ print("Failed to generate DKIM private key for domain {}: {}"
+ .format(domain.name, smart_text(output)))
+ domain.dkim_private_key_path = pkey_path
+ code, output = sysutils.exec_cmd(
+ "openssl rsa -in {} -pubout".format(pkey_path))
+ if code:
+ print("Failed to generate DKIM public key for domain {}: {}"
+ .format(domain.name, smart_text(output)))
+ public_key = ""
+ for cpt, line in enumerate(smart_text(output).splitlines()):
+ if cpt == 0 or line.startswith("-----"):
+ continue
+ public_key += line
+ domain.dkim_public_key = public_key
+ domain.save(update_fields=["dkim_public_key", "dkim_private_key_path"])
+
+ def handle(self, *args, **options):
+ """Entry point."""
+ self.default_key_length = param_tools.get_global_parameter(
+ "dkim_default_key_length")
+ qset = models.Domain.objects.filter(
+ enable_dkim=True, dkim_private_key_path="")
+ for domain in qset:
+ self.create_new_dkim_key(domain)
diff --git a/modoboa/admin/migrations/0012_auto_20180112_1658.py b/modoboa/admin/migrations/0012_auto_20180112_1658.py
new file mode 100644
index 000000000..a69e64388
--- /dev/null
+++ b/modoboa/admin/migrations/0012_auto_20180112_1658.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.8 on 2018-01-12 15:58
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('admin', '0011_domain_transport'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='domain',
+ name='dkim_key_length',
+ field=models.PositiveIntegerField(blank=True, choices=[(1024, '1024'), (2048, '2048'), (4096, '4096')], null=True, verbose_name='Key length'),
+ ),
+ migrations.AddField(
+ model_name='domain',
+ name='dkim_key_selector',
+ field=models.CharField(default='modoboa', max_length=30, verbose_name='Key selector'),
+ ),
+ migrations.AddField(
+ model_name='domain',
+ name='dkim_private_key_path',
+ field=models.CharField(blank=True, max_length=254),
+ ),
+ migrations.AddField(
+ model_name='domain',
+ name='dkim_public_key',
+ field=models.TextField(blank=True),
+ ),
+ migrations.AddField(
+ model_name='domain',
+ name='enable_dkim',
+ field=models.BooleanField(default=False, help_text='If you activate this feature, a DKIM key will be generated for this domain.', verbose_name='Enable DKIM signing'),
+ ),
+ ]
diff --git a/modoboa/admin/models/domain.py b/modoboa/admin/models/domain.py
index 04c1f3518..fa0337a3a 100644
--- a/modoboa/admin/models/domain.py
+++ b/modoboa/admin/models/domain.py
@@ -58,6 +58,21 @@ class Domain(AdminObject):
transport = models.OneToOneField(
"transport.Transport", null=True, on_delete=models.SET_NULL)
+ enable_dkim = models.BooleanField(
+ ugettext_lazy("Enable DKIM signing"),
+ help_text=ugettext_lazy(
+ "If you activate this feature, a DKIM key will be "
+ "generated for this domain."),
+ default=False
+ )
+ dkim_key_selector = models.CharField(max_length=30, default="modoboa")
+ dkim_key_length = models.PositiveIntegerField(
+ ugettext_lazy("Key length"), choices=constants.DKIM_KEY_LENGTHS,
+ blank=True, null=True
+ )
+ dkim_public_key = models.TextField(blank=True)
+ dkim_private_key_path = models.CharField(max_length=254, blank=True)
+
class Meta:
permissions = (
("view_domain", "View domain"),
@@ -187,6 +202,12 @@ class Domain(AdminObject):
return 0
return int(self.used_quota / float(self.quota) * 100)
+ @property
+ def bind_format_dkim_public_key(self):
+ """Return DKIM public key using Bind format."""
+ return "{}._domainkey 10800 IN TXT \"v=DKIM1; k=rsa; p={}\"".format(
+ self.dkim_key_selector, self.dkim_public_key)
+
def add_admin(self, account):
"""Add a new administrator to this domain.
diff --git a/modoboa/admin/static/admin/js/admin.js b/modoboa/admin/static/admin/js/admin.js
index 47ce73aab..b198b9ab7 100644
--- a/modoboa/admin/static/admin/js/admin.js
+++ b/modoboa/admin/static/admin/js/admin.js
@@ -223,6 +223,21 @@ Domains.prototype = {
"disabled", $target.val() === "False");
},
+ /**
+ * Hide or show password inputs depending on value.
+ *
+ * @param {bool} value - visibility flag
+ */
+ toggleDKIMFields: function (value) {
+ if (value) {
+ $('#id_dkim_key_selector').parents(".form-group").show();
+ $('#id_dkim_key_length').parents(".form-group").show();
+ } else {
+ $('#id_dkim_key_selector').parents(".form-group").hide();
+ $('#id_dkim_key_length').parents(".form-group").hide();
+ }
+ },
+
/**
* Initialize the main form contained in the domain edition modal.
*
@@ -239,6 +254,11 @@ Domains.prototype = {
return true;
}
});
+ $('#id_enable_dkim').click($.proxy(function (e) {
+ var $this = $(e.target);
+ this.toggleDKIMFields($this.prop('checked'));
+ }, this));
+ this.toggleDKIMFields($('#id_enable_dkim').prop('checked'));
},
optionsform_init: function() {
diff --git a/modoboa/admin/templates/admin/_domain_dkim_key.html b/modoboa/admin/templates/admin/_domain_dkim_key.html
new file mode 100644
index 000000000..d867ac509
--- /dev/null
+++ b/modoboa/admin/templates/admin/_domain_dkim_key.html
@@ -0,0 +1,23 @@
+{% load i18n %}
+
+
+
+
+
+
+ {% trans "Raw format" %}
+ {{ domain.dkim_public_key }}
+
+
+ {% trans "Bind/named format" %}
+ {{ domain.bind_format_dkim_public_key }}
+
+
+
+
+
diff --git a/modoboa/admin/templates/admin/domain_detail.html b/modoboa/admin/templates/admin/domain_detail.html
index a4e7e21bd..96dafeb9c 100644
--- a/modoboa/admin/templates/admin/domain_detail.html
+++ b/modoboa/admin/templates/admin/domain_detail.html
@@ -25,14 +25,6 @@
{% trans "Summary" %}
- {% if enable_mx_checks or enable_dnsbl_checks %}
-
- | {% trans "DNS status" %} |
-
- {% include "admin/_domain_dnschecks_status.html" %}
- |
-
- {% endif %}
| {% trans "Creation date" %} |
{{ domain.creation }} |
@@ -94,6 +86,33 @@
+
+
+
{% trans "DNS" %}
+
+
+ {% if enable_mx_checks or enable_dnsbl_checks %}
+
+ | {% trans "Status" %} |
+
+ {% include "admin/_domain_dnschecks_status.html" %}
+ |
+
+ {% endif %}
+
+ | {% trans "DKIM key" %} |
+
+ {% if domain.dkim_public_key %}
+
+ {% else %}
+ {% trans "Not generated" %}
+ {% endif %}
+ |
+
+
+
{% trans "Administrators" %}
@@ -101,19 +120,19 @@
| {% trans "Username" %} | {% trans "Name" %} | |
- {% for da in domain.admins %}
-
- | {{ da.username }} |
- {{ da.fullname }} |
-
- {% if perms.admin.change_domain %}
-
- {% endif %}
- |
-
- {% empty %}
- | {% trans "No domain administrator defined yet." %} |
- {% endfor %}
+ {% for da in domain.admins %}
+
+ | {{ da.username }} |
+ {{ da.fullname }} |
+
+ {% if perms.admin.change_domain %}
+
+ {% endif %}
+ |
+
+ {% empty %}
+ | {% trans "No domain administrator defined yet." %} |
+ {% endfor %}
@@ -123,4 +142,9 @@
{% endfor %}
+ {% if domain.dkim_public_key %}
+
+ {% include "admin/_domain_dkim_key.html" %}
+
+ {% endif %}
{% endblock %}
diff --git a/modoboa/admin/templates/admin/domain_general_form.html b/modoboa/admin/templates/admin/domain_general_form.html
index 4caca3a92..f95838700 100644
--- a/modoboa/admin/templates/admin/domain_general_form.html
+++ b/modoboa/admin/templates/admin/domain_general_form.html
@@ -6,3 +6,6 @@
{% render_fields_group form "aliases" %}
{% render_field form.enabled %}
{% render_field form.enable_dns_checks %}
+{% render_field form.enable_dkim %}
+{% render_field form.dkim_key_selector %}
+{% render_field form.dkim_key_length %}
diff --git a/modoboa/admin/tests/test_domain.py b/modoboa/admin/tests/test_domain.py
index da61d7cea..cabab25e3 100644
--- a/modoboa/admin/tests/test_domain.py
+++ b/modoboa/admin/tests/test_domain.py
@@ -4,13 +4,21 @@
from __future__ import unicode_literals
+import os
+import shutil
+import tempfile
+
import dns.resolver
from mock import patch
+from testfixtures import compare
+from django.core.management import call_command
from django.urls import reverse
+from django.utils.html import escape
from modoboa.core import factories as core_factories
from modoboa.core.models import User
+from modoboa.core.tests.test_views import SETTINGS_SAMPLE
from modoboa.lib.tests import ModoTestCase
from . import utils
@@ -213,7 +221,8 @@ class DomainTestCase(ModoTestCase):
mock_query.side_effect = utils.mock_dns_query_result
mock_getaddrinfo.side_effect = utils.mock_ip_query_result
values = {
- "name": "no-mx.example.com", "quota": 0, "default_mailbox_quota": 0,
+ "name": "no-mx.example.com", "quota": 0,
+ "default_mailbox_quota": 0,
"create_dom_admin": True, "dom_admin_username": "toto",
"create_aliases": True, "type": "domain", "stepid": "step3",
"with_mailbox": True
@@ -347,3 +356,66 @@ class DomainTestCase(ModoTestCase):
content = response.content.decode('utf-8')
self.assertNotIn("Global statistics", content)
self.assertIn("Per-domain statistics", content)
+
+
+class DKIMTestCase(ModoTestCase):
+ """Test case for DKIM."""
+
+ @classmethod
+ def setUpTestData(cls): # noqa: N802
+ """Create test data."""
+ super(DKIMTestCase, cls).setUpTestData()
+ factories.populate_database()
+
+ def setUp(self):
+ super(DKIMTestCase, self).setUp()
+ self.workdir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ shutil.rmtree(self.workdir)
+
+ def test_global_settings(self):
+ """Check validation rules."""
+ url = reverse("core:parameters")
+ settings = SETTINGS_SAMPLE.copy()
+ settings["admin-dkim_keys_storage_dir"] = "/wrong"
+ response = self.client.post(url, settings, format="json")
+ self.assertEqual(response.status_code, 400)
+ compare(response.json(), {
+ "form_errors": {"dkim_keys_storage_dir": ["Directory not found."]},
+ "prefix": "admin"
+ })
+ settings["admin-dkim_keys_storage_dir"] = self.workdir
+ response = self.client.post(url, settings, format="json")
+ self.assertEqual(response.status_code, 200)
+
+ def test_dkim_key_creation(self):
+ """Test DKIM key creation."""
+ values = {
+ "name": "pouet.com", "quota": 1000, "default_mailbox_quota": 100,
+ "create_dom_admin": False, "type": "domain", "stepid": "step3",
+ "enable_dkim": True, "dkim_key_selector": ""
+ }
+ response = self.ajax_post(reverse("admin:domain_add"), values, 400)
+ self.assertEqual(
+ response["form_errors"]["enable_dkim"][0],
+ "DKIM keys storage directory not configured")
+
+ self.set_global_parameter("dkim_keys_storage_dir", self.workdir)
+ response = self.ajax_post(reverse("admin:domain_add"), values, 400)
+ self.assertEqual(
+ response["form_errors"]["dkim_key_selector"][0],
+ "This field is required.")
+
+ values["dkim_key_selector"] = "default"
+ self.ajax_post(reverse("admin:domain_add"), values)
+
+ call_command("modo", "manage_dkim_keys")
+ key_path = os.path.join(self.workdir, "{}.pem".format(values["name"]))
+ self.assertTrue(os.path.exists(key_path))
+
+ domain = Domain.objects.get(name=values["name"])
+ url = reverse("admin:domain_detail", args=[domain.pk])
+ response = self.client.get(url)
+ self.assertContains(
+ response, escape(domain.bind_format_dkim_public_key))
diff --git a/modoboa/core/migrations/0015_auto_20180112_1642.py b/modoboa/core/migrations/0015_auto_20180112_1642.py
new file mode 100644
index 000000000..d253aaa52
--- /dev/null
+++ b/modoboa/core/migrations/0015_auto_20180112_1642.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.8 on 2018-01-12 15:42
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0014_auto_20171010_1746'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='user',
+ name='language',
+ field=models.CharField(choices=[('cs', '\u010de\u0161tina'), ('de', 'deutsch'), ('en', 'english'), ('el_GR', '\u03b5\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac'), ('es', 'espa\xf1ol'), ('fr', 'fran\xe7ais'), ('it', 'italiano'), ('ja_JP', '\u65e5\u672c\u306e'), ('nl', 'nederlands'), ('pt_PT', 'portugu\xeas'), ('pt_BR', 'portugu\xeas (BR)'), ('pl_PL', 'polski'), ('ru', '\u0440\u0443\u0441\u0441\u043a\u0438\u0439'), ('sv', 'svenska')], default='en', help_text='Prefered language to display pages.', max_length=10, verbose_name='language'),
+ ),
+ ]
diff --git a/modoboa/core/tests/test_views.py b/modoboa/core/tests/test_views.py
index 0c433c16b..c9d5a004a 100644
--- a/modoboa/core/tests/test_views.py
+++ b/modoboa/core/tests/test_views.py
@@ -15,6 +15,63 @@ from .. import factories
from .. import models
from .. import signals
+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",
+ "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",
+ "admin-default_mailbox_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": "none",
+ "core-ldap_user_dn_template": "",
+ "core-default_password": "Toto1000",
+ "limits-deflt_user_quota_limit": "0",
+ "core-hide_features_widget": "False",
+ "core-inactive_account_threshold": "30",
+ "admin-domains_must_have_authorized_mx": False,
+ "core-random_password_length": "8",
+ "admin-create_alias_on_mbox_rename": False,
+ "admin-dkim_keys_storage_dir": "",
+ "admin-dkim_default_key_length": 1024
+}
+
def announcement(sender, location, **kwargs):
"""Simpler handler."""
@@ -106,61 +163,6 @@ class DashboardTestCase(ModoTestCase):
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",
- "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",
- "admin-default_mailbox_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": "none",
- "core-ldap_user_dn_template": "",
- "core-default_password": "Toto1000",
- "limits-deflt_user_quota_limit": "0",
- "core-hide_features_widget": "False",
- "core-inactive_account_threshold": "30",
- "admin-domains_must_have_authorized_mx": False,
- "core-random_password_length": "8",
- "admin-create_alias_on_mbox_rename": False,
- }
-
def test_get_settings(self):
"""Test settings display."""
url = reverse("core:parameters")
@@ -171,10 +173,11 @@ class SettingsTestCase(ModoTestCase):
def test_save_settings(self):
"""Test settings save."""
url = reverse("core:parameters")
- response = self.client.post(url, self.settings_sample, format="json")
+ settings = SETTINGS_SAMPLE.copy()
+ response = self.client.post(url, settings, format="json")
self.assertEqual(response.status_code, 200)
- self.settings_sample["core-rounds_number"] = ""
- response = self.client.post(url, self.settings_sample, format="json")
+ settings["core-rounds_number"] = ""
+ response = self.client.post(url, settings, format="json")
self.assertEqual(response.status_code, 400)
compare(response.json(), {
"form_errors": {"rounds_number": ["This field is required."]},
diff --git a/modoboa/static/css/custom.css b/modoboa/static/css/custom.css
index 63252714a..1db572383 100644
--- a/modoboa/static/css/custom.css
+++ b/modoboa/static/css/custom.css
@@ -298,4 +298,4 @@ table td[name="actions"] {
/* Dashboard */
.slogan {
padding-top: 15px;
-}
\ No newline at end of file
+}
diff --git a/modoboa/static/js/twocols_nav.js b/modoboa/static/js/twocols_nav.js
index 85d164401..3401e0933 100644
--- a/modoboa/static/js/twocols_nav.js
+++ b/modoboa/static/js/twocols_nav.js
@@ -36,7 +36,7 @@ TwocolsNav.prototype = {
success_cb: $.proxy(this.save_cb, this)
});
}, this));
- $(document).on('submit', '#parameters_form', function(evt) {
+ $(document).on('submit', '#parameters_form', $.proxy(function(evt) {
simple_ajax_form_post(evt, {
formid: $(this).attr('id'),
modal: false,
@@ -46,7 +46,7 @@ TwocolsNav.prototype = {
$('a[data-app="' + data.prefix + '"]').tab('show');
}
});
- });
+ }, this));
},
update_content: function(data) {