diff --git a/doc/configuration.rst b/doc/configuration.rst index df9bb4b5c..e41a5caf7 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -31,7 +31,7 @@ The *admin* application exposes several parameters, they are presented below: |Authentication type |General |The backend used for |Local | | | |authentication | | +--------------------+---------------------+-------------------------------+--------------------+ -|Default password |General |Scheme used to crypt |crypt | +|Default password |General |Scheme used to crypt |sha512crypt | |scheme | |mailbox passwords | | +--------------------+---------------------+-------------------------------+--------------------+ |Rounds |General |Number of rounds (only used by |70000 | @@ -144,6 +144,13 @@ The *admin* application exposes several parameters, they are presented below: | | | | | +--------------------+---------------------+-------------------------------+--------------------+ +.. warning:: + + If Dovecot is not running on the same host than Modoboa, you will have + to define which password schemes are supported. To do so, open the + :file:`settings.py` file and add a ``DOVECOT_SUPPORTED_SCHEMES`` + variable with the output of the command: ``doveadm pw -l``. + .. note:: If you are not familiar with virtual domain hosting, you should @@ -202,7 +209,11 @@ Host configuration .. note:: This section is only relevant when Modoboa handles mailboxes - renaming and removal from the filesystem. + renaming and removal from the filesystem, which requires that + Dovecot is installed and running on this host. If it is installed + at a non-standard directory, paths to its binaries can be set in the + :file:`settings.py` file with the ``DOVECOT_LOOKUP_PATH`` and + ``DOVEADM_LOOKUP_PATH`` variables. To manipulate mailboxes on the filesystem, you must allow the user who runs Modoboa to execute commands as the user who owns mailboxes. diff --git a/modoboa/admin/migrations/0019_auto_20210130_2226.py b/modoboa/admin/migrations/0019_auto_20210130_2226.py new file mode 100644 index 000000000..7f3c902e7 --- /dev/null +++ b/modoboa/admin/migrations/0019_auto_20210130_2226.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.17 on 2021-01-30 22:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin', '0018_update_disabled_accounts_aliases'), + ] + + operations = [ + migrations.AlterField( + model_name='domain', + name='name', + field=models.CharField(help_text='The domain name', max_length=253, unique=True, verbose_name='name'), + ), + ] diff --git a/modoboa/admin/models/domain.py b/modoboa/admin/models/domain.py index b7db53700..282f9f887 100644 --- a/modoboa/admin/models/domain.py +++ b/modoboa/admin/models/domain.py @@ -23,7 +23,7 @@ from . import mixins class Domain(mixins.MessageLimitMixin, AdminObject): """Mail domain.""" - name = models.CharField(ugettext_lazy("name"), max_length=100, unique=True, + name = models.CharField(ugettext_lazy("name"), max_length=253, unique=True, help_text=ugettext_lazy("The domain name")) quota = models.PositiveIntegerField( default=0, diff --git a/modoboa/core/commands/templates/settings.py.tpl b/modoboa/core/commands/templates/settings.py.tpl index cab8a30e3..d393e9500 100644 --- a/modoboa/core/commands/templates/settings.py.tpl +++ b/modoboa/core/commands/templates/settings.py.tpl @@ -114,15 +114,15 @@ MIDDLEWARE = ( ) AUTHENTICATION_BACKENDS = ( - # 'modoboa.lib.authbackends.LDAPBackend', - # 'modoboa.lib.authbackends.SMTPBackend', + #'modoboa.lib.authbackends.LDAPBackend', + #'modoboa.lib.authbackends.SMTPBackend', 'django.contrib.auth.backends.ModelBackend', ) # SMTP authentication -# AUTH_SMTP_SERVER_ADDRESS = 'localhost' -# AUTH_SMTP_SERVER_PORT = 25 -# AUTH_SMTP_SECURED_MODE = None # 'ssl' or 'starttls' are accepted +#AUTH_SMTP_SERVER_ADDRESS = 'localhost' +#AUTH_SMTP_SERVER_PORT = 25 +#AUTH_SMTP_SECURED_MODE = None # 'ssl' or 'starttls' are accepted TEMPLATES = [ @@ -202,9 +202,15 @@ SPECTACULAR_SETTINGS = { } # Modoboa settings -# MODOBOA_CUSTOM_LOGO = os.path.join(MEDIA_URL, "custom_logo.png") -# DOVECOT_LOOKUP_PATH = ('/path/to/dovecot', ) +#MODOBOA_CUSTOM_LOGO = os.path.join(MEDIA_URL, "custom_logo.png") + +# Path to Dovecot binaries in case of a non-standard installation +#DOVECOT_LOOKUP_PATH = ('/path/to/dovecot', ) +#DOVEADM_LOOKUP_PATH = ('/path/to/doveadm', ) + +# List of supported schemes if doveadm is not available, given by: doveadm pw -l +#DOVECOT_SUPPORTED_SCHEMES = 'SHA512-CRYPT SHA256-CRYPT' MODOBOA_API_URL = 'https://api.modoboa.org/1/' diff --git a/modoboa/core/password_hashers/__init__.py b/modoboa/core/password_hashers/__init__.py index dbb399731..0a0b3f778 100644 --- a/modoboa/core/password_hashers/__init__.py +++ b/modoboa/core/password_hashers/__init__.py @@ -2,6 +2,7 @@ Password hashers for Modoboa. """ +from django.conf import settings from django.utils.encoding import smart_text from modoboa.core.password_hashers.advanced import ( # NOQA:F401 @@ -32,17 +33,21 @@ def get_password_hasher(scheme): def get_dovecot_schemes(): - """Return schemes supported by dovecot""" - supported_schemes = None - try: - _, schemes = doveadm_cmd("pw -l") - except OSError: - pass - else: - supported_schemes = ["{{{}}}".format(smart_text(scheme)) - for scheme in schemes.split()] + """Return schemes supported by Dovecot. - if not supported_schemes: - supported_schemes = ['{PLAIN}'] + It first try to get supported schemes fron the settings, then from the + doveadm output, and fallback to {MD5-CRYPT} and {PLAIN} if the command + is not found. - return supported_schemes + :return: A list of supported '{SCHEME}' + """ + schemes = getattr(settings, "DOVECOT_SUPPORTED_SCHEMES", None) + + if not schemes: + try: + _, schemes = doveadm_cmd("pw -l") + except OSError: + schemes = "MD5-CRYPT PLAIN" + + return ["{{{}}}".format(smart_text(scheme)) + for scheme in schemes.split()] diff --git a/modoboa/core/tests/test_authentication.py b/modoboa/core/tests/test_authentication.py index 567edc01a..f7aa8b3ab 100644 --- a/modoboa/core/tests/test_authentication.py +++ b/modoboa/core/tests/test_authentication.py @@ -19,9 +19,9 @@ from modoboa.core.password_hashers import ( from modoboa.lib.tests import NO_SMTP, ModoTestCase from .. import factories, models +DOVEADM_TEST_PATH = "{}/doveadm".format(os.path.dirname(__file__)) + -@override_settings( - DOVEADM_LOOKUP_PATH=["{}/doveadm".format(os.path.dirname(__file__))]) class AuthenticationTestCase(ModoTestCase): """Validate authentication scenarios.""" @@ -89,6 +89,7 @@ class AuthenticationTestCase(ModoTestCase): self.assertEqual(response.status_code, 302) self.assertTrue(response.url.endswith(reverse("core:dashboard"))) + @override_settings(DOVEADM_LOOKUP_PATH=[DOVEADM_TEST_PATH]) def test_password_schemes(self): """Validate password scheme changes.""" username = "user@test.com" @@ -130,6 +131,7 @@ class AuthenticationTestCase(ModoTestCase): self.assertTrue(user.password.startswith(pw_hash.scheme)) @skipUnless(argon2, "argon2-cffi not installed") + @override_settings(DOVEADM_LOOKUP_PATH=[DOVEADM_TEST_PATH]) def test_password_argon2_parameter_change(self): """Validate hash parameter update on login works with argon2.""" username = "user@test.com" @@ -169,8 +171,9 @@ class AuthenticationTestCase(ModoTestCase): self.assertEqual(parameters.memory_cost, 1000) self.assertEqual(parameters.parallelism, 2) - def test_supported_schemes(self): - """Validate dovecot supported schemes.""" + @override_settings(DOVEADM_LOOKUP_PATH=[DOVEADM_TEST_PATH]) + def test_dovecot_supported_schemes(self): + """Validate Dovecot supported schemes with fake command output.""" supported_schemes = get_dovecot_schemes() self.assertEqual(supported_schemes, ["{MD5}", @@ -204,6 +207,17 @@ class AuthenticationTestCase(ModoTestCase): "{SHA256-CRYPT}", "{SHA512-CRYPT}"]) + @override_settings(DOVECOT_SUPPORTED_SCHEMES="SHA1 SHA512-CRYPT") + def test_dovecot_supported_schemes_from_settings(self): + """Validate dovecot supported schemes from the settings.""" + supported_schemes = get_dovecot_schemes() + self.assertEqual(supported_schemes, ["{SHA1}", "{SHA512-CRYPT}"]) + + def test_dovecot_default_schemes(self): + """Check default scheme if doveadm is not found.""" + supported_schemes = get_dovecot_schemes() + self.assertEqual(supported_schemes, ["{MD5-CRYPT}", "{PLAIN}"]) + class PasswordResetTestCase(ModoTestCase): """Test password reset service.""" diff --git a/setup.cfg b/setup.cfg index 427c34916..947d97f8f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,3 +21,5 @@ max-line-length = 80 [flake8] exclude = migrations max-line-length = 80 +per-file-ignores = + modoboa/core/commands/templates/settings.py.tpl: E265