Merge branch 'master' of github.com:modoboa/modoboa

This commit is contained in:
Antoine Nguyen
2020-05-05 21:36:54 +02:00
10 changed files with 239 additions and 2 deletions
+43
View File
@@ -193,6 +193,49 @@ SQLite users
Replace values between ``<>`` with yours.
LDAP
====
To make the LDAP authentication work, edit the :file:`conf.d/10-auth.conf` and
uncomment the following line at the end::
!include auth-ldap.conf.ext
Then edit the :file:`conf.d/auth-ldap.conf.ext` and edit the passdb section as following.
You should comment the userdb section, which will be managed by SQL with modoboa database.::
passdb {
driver = ldap
# Path for LDAP configuration file, see example-config/dovecot-ldap.conf.ext
args = /etc/dovecot/dovecot-ldap.conf.ext
}
#userdb {
#driver = ldap
#args = /etc/dovecot/dovecot-ldap.conf.ext
# Default fields can be used to specify defaults that LDAP may override
#default_fields = home=/home/virtual/%u
#}
Your own dovecot LDAP configuration file is now :file:`/etc/dovecot/dovecot-ldap.conf.ext`.
You can add your default LDAP conf in it, following the `official documentation <https://doc.dovecot.org/configuration_manual/authentication/ldap/>`_.
Synchronize dovecot LDAP conf with modoboa LDAP conf
----------------------------------------------------
To make dovecot LDAP configuration synchronized with modoboa LDAP configuration, you should create a dedicated dovecot conf file.
At the end of your dovecot configuration file (:file:`dovecot-ldap.conf.ext`), add the following line::
!include_try dovecot-modoboa.conf.ext
Then, set modoboa parameter *Enable Dovecot LDAP sync* to *Yes*.
Then set the *Dovecot LDAP config file* following the previous step (*/etc/dovecot/dovecot-modoboa.conf.ext* in the example)
The last step is to add the command **update_dovecot_conf** to the cron job of modoboa.
Then, each time your modoboa LDAP configuration is updated, your dovecot LDAP configuration will also be.
LMTP
====
@@ -0,0 +1,84 @@
"""Management command tests."""
import os
import shutil
import tempfile
from unittest import mock
from django.core.management import call_command
from django.test import override_settings
from django.urls import reverse
from django.db import transaction
from modoboa.core.tests.test_views import SETTINGS_SAMPLE
from modoboa.lib.tests import ModoTestCase
from .. import factories, models
from modoboa.core import models as core_models
class NeedDovecotUpdateTestCase(ModoTestCase):
"""Test need dovecot ldap update command."""
@classmethod
def setUpTestData(cls):
"""Create test data."""
super(NeedDovecotUpdateTestCase, cls).setUpTestData()
factories.populate_database()
def setUp(self):
"""Initiate initial env."""
super(NeedDovecotUpdateTestCase, self).setUp()
self.workdir = tempfile.mkdtemp()
def tearDown(self):
"""Reset test env."""
shutil.rmtree(self.workdir)
def test_update_dovecot_update_state_valid_form(self):
self.assertFalse(self.localconfig.need_dovecot_update)
url = reverse("core:parameters")
settings = SETTINGS_SAMPLE.copy()
response = self.client.post(url, settings, format="json")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), "Parameters saved")
self.localconfig.refresh_from_db()
self.assertTrue(self.localconfig.need_dovecot_update)
def test_update_dovecot_ldap_conf(self):
self.localconfig.need_dovecot_update = True
self.localconfig.save()
self.assertTrue(self.localconfig.need_dovecot_update)
tmp_file = os.path.join(self.workdir, "test-dovecot-ldap.conf")
self.set_global_parameters({
"authentication_type": "ldap",
"ldap_dovecot_sync": True,
"ldap_dovecot_conf_file": tmp_file,
"ldap_server_address": "localhost",
"ldap_server_port": "636",
"ldap_enable_secondary_server": True,
"ldap_secondary_server_address": "localhost2",
"ldap_secondary_server_port": "636",
"ldap_secured": "ssl",
"ldap_bind_dn": "DC=test,DC=lan",
"ldap_bind_password": "test",
"ldap_search_base": "CN=Users,DC=test,DC=lan",
"ldap_search_filter": "(& (objectClass=user) (|(mail=%(user)s)(sAMAccountName=%(user)s)) )"
}, app="core")
# Generated file checks
call_command("update_dovecot_conf")
self.assertTrue(os.path.exists(tmp_file))
with open(tmp_file) as tmp:
content = tmp.read()
self.assertIn("uris = ldaps://localhost:636 ldaps://localhost2:636", content)
self.assertIn("dn = \"DC=test,DC=lan\"", content)
self.assertIn("dnpass = \'test\'", content)
self.assertIn("base = CN=Users,DC=test,DC=lan", content)
self.assertIn("user_filter = (& (objectClass=user) (|(mail=%u)(sAMAccountName=%u)) )", content)
self.assertIn("pass_filter = (& (objectClass=user) (|(mail=%u)(sAMAccountName=%u)) )", content)
self.localconfig.refresh_from_db()
self.assertFalse(self.localconfig.need_dovecot_update)
+22 -1
View File
@@ -18,7 +18,6 @@ from modoboa.parameters import forms as param_forms, tools as param_tools
from . import constants
from . import sms_backends
def enabled_applications():
"""Return the list of installed extensions."""
from modoboa.core.extensions import exts_pool
@@ -369,6 +368,23 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
),
)
ldap_dovecot_sync = YesNoField(
label=ugettext_lazy("Enable Dovecot LDAP sync"),
initial=False,
help_text=ugettext_lazy(
"Enable synchronisation of Dovecot LDAP parameters from Modoboa."
)
)
ldap_dovecot_conf_file = forms.CharField(
label=ugettext_lazy("Dovecot LDAP config file"),
initial="/etc/dovecot/dovecot-modoboa.conf",
required=False,
help_text=ugettext_lazy(
"Dovecot LDAP configuration file location"
)
)
dash_sep = SeparatorField(label=ugettext_lazy("Dashboard"))
rss_feed_url = forms.URLField(
@@ -495,6 +511,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
"ldap_import_search_base": "ldap_enable_import=True",
"ldap_import_search_filter": "ldap_enable_import=True",
"ldap_import_username_attr": "ldap_enable_import=True",
"ldap_dovecot_conf_file": "ldap_dovecot_sync=True",
"check_new_versions": "enable_api_communication=True",
"send_statistics": "enable_api_communication=True",
"send_new_versions_email": "check_new_versions=True",
@@ -682,3 +699,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
from modoboa.lib.authbackends import LDAPSecondaryBackend
self._apply_ldap_settings(values, LDAPSecondaryBackend)
def to_dovecot_settings(self):
self.localconfig.need_dovecot_update = True
self.localconfig.save()
@@ -0,0 +1,18 @@
# Generated by Django 2.2.10 on 2020-04-29 12:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0020_auto_20200421_0851'),
]
operations = [
migrations.AddField(
model_name='localconfig',
name='need_dovecot_update',
field=models.BooleanField(default=False),
),
]
+3
View File
@@ -446,6 +446,9 @@ class LocalConfig(models.Model):
_parameters = jsonfield.JSONField(default={})
# Dovecot LDAP update
need_dovecot_update = models.BooleanField(default=False)
def __init__(self, *args, **kwargs):
"""Load parameter manager."""
super(LocalConfig, self).__init__(*args, **kwargs)
+2 -1
View File
@@ -88,7 +88,8 @@ SETTINGS_SAMPLE = {
"core-sms_password_recovery": False,
"core-sms_provider": "",
"core-sms_ovh_endpoint": "ovh-eu",
"core-password_recovery_msg": ""
"core-password_recovery_msg": "",
"core-ldap_dovecot_sync": False
}
+1
View File
@@ -35,6 +35,7 @@ def parameters(request, tplname="core/parameters.html"):
if form.is_valid():
form.save()
form.to_django_settings()
form.to_dovecot_settings()
continue
return render_to_json_response(
{"form_errors": form.errors, "prefix": form.app}, status=400
+38
View File
@@ -240,3 +240,41 @@ def import_accounts_from_ldap(config):
user.save()
# FIXME: handle delete and rename operations?
def build_ldap_uri(config, node=""):
""" Building LDAP uris for dovecot conf """
if node:
node += "_"
return "{}://{}:{}".format(
"ldaps" if config["ldap_secured"]=="ssl" else "ldap",
config["ldap_{}server_address".format(node)],
config["ldap_{}server_port".format(node)]
)
def update_dovecot_config_file(config):
""" Update dovecot configuration file from LDAP parameters """
conf_file = config["ldap_dovecot_conf_file"]
# Hosts conf
uris = build_ldap_uri(config)
if config["ldap_enable_secondary_server"]:
uris += " " + build_ldap_uri(config, "secondary")
# Auth conf
bind_dn = config["ldap_bind_dn"]
bind_pwd = config["ldap_bind_password"]
# Search conf
base = config["ldap_search_base"]
user_filter = config["ldap_search_filter"].replace("(user)s","u")
with open(conf_file, "w") as fp:
fp.write("""uris = {uris}
dn = "{bind_dn}"
dnpass = '{bind_pwd}'
base = {base}
user_filter = {user_filter}
pass_filter = {pass_filter}
""".format(uris=uris, bind_dn=bind_dn, bind_pwd=bind_pwd, base=base, user_filter=user_filter, pass_filter=user_filter)
)
@@ -0,0 +1,25 @@
"""Command to populate local database from a remote LDAP directory."""
from django.core.management.base import BaseCommand
from django.conf import settings
from modoboa.parameters import tools as param_tools
from ... import lib
from modoboa.core import models
class Command(BaseCommand):
"""Command definition."""
help = "Update dovecot configuration file from modoboa LDAP parameters"
def handle(self, *args, **options):
"""Command entry point."""
localconfig = models.LocalConfig.objects.first()
if localconfig.need_dovecot_update:
config = dict(param_tools.get_global_parameters("core"))
if config["authentication_type"] == "ldap" and config["ldap_dovecot_sync"]:
lib.update_dovecot_config_file(config)
localconfig.need_dovecot_update = False
localconfig.save()
+3
View File
@@ -87,6 +87,9 @@ class AdminParametersForm(GenericParametersForm):
"""Inject parameters into django settings module."""
pass
def to_dovecot_settings(self):
pass
class UserParametersForm(GenericParametersForm):
"""Base form to declare user level parameters."""