From 69a5a2c243ed6734df9ef0ad853fa2f0f963b229 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Fri, 12 Oct 2018 22:30:22 +0200 Subject: [PATCH 1/9] Do not try to decode empty string. --- modoboa/lib/email_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modoboa/lib/email_utils.py b/modoboa/lib/email_utils.py index 2ea160a91..3eb25b9f0 100644 --- a/modoboa/lib/email_utils.py +++ b/modoboa/lib/email_utils.py @@ -130,6 +130,8 @@ class Email(object): if header in msg: decoded_values = [] for value, encoding in email.header.decode_header(msg[header]): + if not len(value): + continue if encoding: value = smart_text(value, encoding=encoding) elif isinstance(value, six.binary_type): From fcb3208e20c1df3ab2a66bade622daa3a0e991a1 Mon Sep 17 00:00:00 2001 From: Anthony Bourguignon Date: Sat, 13 Oct 2018 11:47:29 +0200 Subject: [PATCH 2/9] check for dovecot supported password schemes call doveadm pw -l to list password schemes only show supported password schemes --- modoboa/core/app_settings.py | 13 +++++------ modoboa/core/password_hashers/__init__.py | 27 +++++++++++++++++++++++ modoboa/core/password_hashers/advanced.py | 12 ++++++++++ modoboa/core/password_hashers/base.py | 16 ++++++++++++++ 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index 788983144..73061befa 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -7,6 +7,8 @@ from django.conf import settings from django.contrib.auth import password_validation from django.utils.translation import ugettext as _, ugettext_lazy +from modoboa.core.password_hashers import get_dovecot_schemes +from modoboa.core.password_hashers.base import PasswordHasher from modoboa.lib import fields as lib_fields from modoboa.lib.form_utils import ( HorizontalRadioSelect, SeparatorField, YesNoField @@ -45,14 +47,9 @@ class GeneralParametersForm(param_forms.AdminParametersForm): password_scheme = forms.ChoiceField( label=ugettext_lazy("Default password scheme"), - choices=[("sha512crypt", "sha512crypt"), - ("sha256crypt", "sha256crypt"), - ("blfcrypt", "bcrypt"), - ("md5crypt", ugettext_lazy("md5crypt (weak)")), - ("sha256", ugettext_lazy("sha256 (weak)")), - ("md5", ugettext_lazy("md5 (weak)")), - ("crypt", ugettext_lazy("crypt (weak)")), - ("plain", ugettext_lazy("plain (weak)"))], + choices=[(hasher.name, ugettext_lazy(hasher.label)) + for hasher in PasswordHasher.get_password_hashers() + if hasher().scheme in get_dovecot_schemes()], initial="sha512crypt", help_text=ugettext_lazy("Scheme used to crypt mailbox passwords"), widget=forms.Select(attrs={"class": "form-control"}) diff --git a/modoboa/core/password_hashers/__init__.py b/modoboa/core/password_hashers/__init__.py index 0eb35c6fa..e8463ae9a 100644 --- a/modoboa/core/password_hashers/__init__.py +++ b/modoboa/core/password_hashers/__init__.py @@ -6,12 +6,17 @@ Password hashers for Modoboa. from __future__ import unicode_literals +import os + +from django.utils.encoding import force_text + from modoboa.core.password_hashers.advanced import ( # NOQA:F401 BLFCRYPTHasher, MD5CRYPTHasher, SHA256CRYPTHasher, SHA512CRYPTHasher ) from modoboa.core.password_hashers.base import ( # NOQA:F401 CRYPTHasher, MD5Hasher, PLAINHasher, SHA256Hasher ) +from modoboa.lib.sysutils import exec_cmd def get_password_hasher(scheme): @@ -29,3 +34,25 @@ def get_password_hasher(scheme): except KeyError: hasher = PLAINHasher return hasher + +def get_dovecot_schemes(): + """Return schemes supported by dovecot""" + supported_schemes = ["{PLAIN}"] + dpath = None + code, output = exec_cmd("which doveadm") + if not code: + dpath = force_text(output).strip() + else: + known_paths = ("/usr/bin/doveadm", "/usr/local/bin/doveadm") + for fpath in known_paths: + if os.path.isfile(fpath) and os.access(fpath, os.X_OK): + dpath = fpath + if dpath: + try: + code, schemes = exec_cmd("%s pw -l" % dpath) + except OSError: + pass + else: + supported_schemes = ["{{{}}}".format(scheme) for scheme in force_text(schemes).split()] + + return supported_schemes diff --git a/modoboa/core/password_hashers/advanced.py b/modoboa/core/password_hashers/advanced.py index 854cfb9ad..40fa9d499 100644 --- a/modoboa/core/password_hashers/advanced.py +++ b/modoboa/core/password_hashers/advanced.py @@ -21,6 +21,9 @@ class BLFCRYPTHasher(PasswordHasher): Supports rounds and provides compatibility with dovecot on *BSD systems. """ + name = "blfcrypt" + label = "blfcrypt" + @property def scheme(self): return "{BLF-CRYPT}" if self._target == "local" else "{CRYPT}" @@ -49,6 +52,9 @@ class MD5CRYPTHasher(PasswordHasher): This scheme can't be considered as secure anymore. """ + name = "md5crypt" + label = "md5crypt" + @property def scheme(self): return "{MD5-CRYPT}" if self._target == "local" else "{CRYPT}" @@ -71,6 +77,9 @@ class SHA256CRYPTHasher(PasswordHasher): Supports rounds and is a good compromise between security and performance. """ + name = "sha256crypt" + label = "sha256crypt" + @property def scheme(self): return "{SHA256-CRYPT}" if self._target == "local" else "{CRYPT}" @@ -95,6 +104,9 @@ class SHA512CRYPTHasher(PasswordHasher): Supports rounds and is the strongest scheme provided by Modoboa. Requires more resource than SHA256-CRYPT. """ + name = "sha512crypt" + label = "sha512crypt" + @property def scheme(self): return "{SHA512-CRYPT}" if self._target == "local" else "{CRYPT}" diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py index 93b316919..1f9b37653 100644 --- a/modoboa/core/password_hashers/base.py +++ b/modoboa/core/password_hashers/base.py @@ -68,12 +68,19 @@ class PasswordHasher(object): hashed_value ) + @classmethod + def get_password_hashers(cls): + return cls.__subclasses__() + class PLAINHasher(PasswordHasher): """ Plain (ie. clear) password hasher. """ + name = "plain" + label = "plain (weak)" + @property def scheme(self): return "{PLAIN}" @@ -89,6 +96,9 @@ class CRYPTHasher(PasswordHasher): Uses python `crypt` standard module. """ + name = "crypt" + label = "crypt (weak)" + @property def scheme(self): return "{CRYPT}" @@ -112,6 +122,9 @@ class MD5Hasher(PasswordHasher): Uses python `hashlib` standard module. """ + name = "md5" + label = "md5 (weak)" + @property def scheme(self): return "{MD5}" @@ -128,6 +141,9 @@ class SHA256Hasher(PasswordHasher): Uses python `hashlib` and `base64` standard modules. """ + name = "sha256" + label = "sha256 (weak)" + @property def scheme(self): return "{SHA256}" From b321228a82c69a34ae7086625b4c7118eff8f5b4 Mon Sep 17 00:00:00 2001 From: Anthony Bourguignon Date: Sat, 13 Oct 2018 12:00:57 +0200 Subject: [PATCH 3/9] Improve code quality --- modoboa/core/app_settings.py | 4 ++-- modoboa/core/password_hashers/__init__.py | 3 ++- modoboa/core/password_hashers/base.py | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index 73061befa..0cf69528a 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -48,8 +48,8 @@ class GeneralParametersForm(param_forms.AdminParametersForm): password_scheme = forms.ChoiceField( label=ugettext_lazy("Default password scheme"), choices=[(hasher.name, ugettext_lazy(hasher.label)) - for hasher in PasswordHasher.get_password_hashers() - if hasher().scheme in get_dovecot_schemes()], + for hasher in PasswordHasher.get_password_hashers() + if hasher().scheme in get_dovecot_schemes()], initial="sha512crypt", help_text=ugettext_lazy("Scheme used to crypt mailbox passwords"), widget=forms.Select(attrs={"class": "form-control"}) diff --git a/modoboa/core/password_hashers/__init__.py b/modoboa/core/password_hashers/__init__.py index e8463ae9a..11e076cf1 100644 --- a/modoboa/core/password_hashers/__init__.py +++ b/modoboa/core/password_hashers/__init__.py @@ -53,6 +53,7 @@ def get_dovecot_schemes(): except OSError: pass else: - supported_schemes = ["{{{}}}".format(scheme) for scheme in force_text(schemes).split()] + supported_schemes = ["{{{}}}".format(scheme) + for scheme in force_text(schemes).split()] return supported_schemes diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py index 1f9b37653..3d8d2b354 100644 --- a/modoboa/core/password_hashers/base.py +++ b/modoboa/core/password_hashers/base.py @@ -70,6 +70,7 @@ class PasswordHasher(object): @classmethod def get_password_hashers(cls): + """Return all the PasswordHasher supported by Modoboa""" return cls.__subclasses__() From 1836cee5fbc415dd7c71633827e89f8825c752a1 Mon Sep 17 00:00:00 2001 From: Anthony Bourguignon Date: Sun, 14 Oct 2018 10:33:58 +0200 Subject: [PATCH 4/9] Improve code quality move doveadm call to sysutils password hashers name and label are now class properties --- modoboa/core/password_hashers/__init__.py | 34 +++++++------------ modoboa/core/password_hashers/advanced.py | 12 +------ modoboa/core/password_hashers/base.py | 28 ++++++++-------- modoboa/lib/sysutils.py | 40 +++++++++++++++++++++++ 4 files changed, 68 insertions(+), 46 deletions(-) diff --git a/modoboa/core/password_hashers/__init__.py b/modoboa/core/password_hashers/__init__.py index 11e076cf1..80a5a8529 100644 --- a/modoboa/core/password_hashers/__init__.py +++ b/modoboa/core/password_hashers/__init__.py @@ -6,17 +6,13 @@ Password hashers for Modoboa. from __future__ import unicode_literals -import os - -from django.utils.encoding import force_text - from modoboa.core.password_hashers.advanced import ( # NOQA:F401 BLFCRYPTHasher, MD5CRYPTHasher, SHA256CRYPTHasher, SHA512CRYPTHasher ) from modoboa.core.password_hashers.base import ( # NOQA:F401 CRYPTHasher, MD5Hasher, PLAINHasher, SHA256Hasher ) -from modoboa.lib.sysutils import exec_cmd +from modoboa.lib.sysutils import doveadm_cmd def get_password_hasher(scheme): @@ -35,25 +31,19 @@ def get_password_hasher(scheme): hasher = PLAINHasher return hasher + def get_dovecot_schemes(): """Return schemes supported by dovecot""" - supported_schemes = ["{PLAIN}"] - dpath = None - code, output = exec_cmd("which doveadm") - if not code: - dpath = force_text(output).strip() + supported_schemes = None + try: + code, schemes = doveadm_cmd("pw -l") + except OSError: + pass else: - known_paths = ("/usr/bin/doveadm", "/usr/local/bin/doveadm") - for fpath in known_paths: - if os.path.isfile(fpath) and os.access(fpath, os.X_OK): - dpath = fpath - if dpath: - try: - code, schemes = exec_cmd("%s pw -l" % dpath) - except OSError: - pass - else: - supported_schemes = ["{{{}}}".format(scheme) - for scheme in force_text(schemes).split()] + supported_schemes = ["{{{}}}".format(scheme) + for scheme in schemes.split()] + + if not supported_schemes: + supported_schemes = ['{PLAIN}'] return supported_schemes diff --git a/modoboa/core/password_hashers/advanced.py b/modoboa/core/password_hashers/advanced.py index 40fa9d499..8aaaf506c 100644 --- a/modoboa/core/password_hashers/advanced.py +++ b/modoboa/core/password_hashers/advanced.py @@ -21,8 +21,6 @@ class BLFCRYPTHasher(PasswordHasher): Supports rounds and provides compatibility with dovecot on *BSD systems. """ - name = "blfcrypt" - label = "blfcrypt" @property def scheme(self): @@ -46,14 +44,12 @@ class BLFCRYPTHasher(PasswordHasher): class MD5CRYPTHasher(PasswordHasher): - """ MD5-CRYPT password hasher. This scheme can't be considered as secure anymore. """ - name = "md5crypt" - label = "md5crypt" + _weak = True @property def scheme(self): @@ -70,15 +66,12 @@ class MD5CRYPTHasher(PasswordHasher): class SHA256CRYPTHasher(PasswordHasher): - """ SHA256-CRYPT password hasher. Supports rounds and is a good compromise between security and performance. """ - name = "sha256crypt" - label = "sha256crypt" @property def scheme(self): @@ -97,15 +90,12 @@ class SHA256CRYPTHasher(PasswordHasher): class SHA512CRYPTHasher(PasswordHasher): - """ SHA512-CRYPT password hasher. Supports rounds and is the strongest scheme provided by Modoboa. Requires more resource than SHA256-CRYPT. """ - name = "sha512crypt" - label = "sha512crypt" @property def scheme(self): diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py index 3d8d2b354..88652de13 100644 --- a/modoboa/core/password_hashers/base.py +++ b/modoboa/core/password_hashers/base.py @@ -19,14 +19,24 @@ from django.utils.encoding import force_bytes, force_text class PasswordHasher(object): - """ Base class of all hashers. """ + _weak = False def __init__(self, target="local"): self._target = target + class __metaclass__(type): + """class properties""" + @property + def name(cls): + return cls.__name__.rstrip('Hasher').lower() + + @property + def label(cls): + return cls.name if not cls._weak else "{} (weak)".format(cls.name) + def _encrypt(self, clearvalue, salt=None): raise NotImplementedError @@ -75,12 +85,10 @@ class PasswordHasher(object): class PLAINHasher(PasswordHasher): - """ Plain (ie. clear) password hasher. """ - name = "plain" - label = "plain (weak)" + _weak = True @property def scheme(self): @@ -91,14 +99,12 @@ class PLAINHasher(PasswordHasher): class CRYPTHasher(PasswordHasher): - """ crypt password hasher. Uses python `crypt` standard module. """ - name = "crypt" - label = "crypt (weak)" + _weak = True @property def scheme(self): @@ -117,14 +123,12 @@ class CRYPTHasher(PasswordHasher): class MD5Hasher(PasswordHasher): - """ MD5 password hasher. Uses python `hashlib` standard module. """ - name = "md5" - label = "md5 (weak)" + _weak = True @property def scheme(self): @@ -136,14 +140,12 @@ class MD5Hasher(PasswordHasher): class SHA256Hasher(PasswordHasher): - """ SHA256 password hasher. Uses python `hashlib` and `base64` standard modules. """ - name = "sha256" - label = "sha256 (weak)" + _weak = True @property def scheme(self): diff --git a/modoboa/lib/sysutils.py b/modoboa/lib/sysutils.py index 3d21445bf..a363643fc 100644 --- a/modoboa/lib/sysutils.py +++ b/modoboa/lib/sysutils.py @@ -8,9 +8,13 @@ This module extra functions/shortcuts to communicate with the system from __future__ import unicode_literals import inspect +import os import re import subprocess +from django.conf import settings +from django.utils.encoding import force_text + def exec_cmd(cmd, sudo_user=None, pinput=None, capture_output=True, **kwargs): """Execute a shell command. @@ -42,6 +46,42 @@ def exec_cmd(cmd, sudo_user=None, pinput=None, capture_output=True, **kwargs): return process.returncode, output +def doveadm_cmd(params, sudo_user=None, pinput=None, capture_output=True, **kwargs): + """Execute doveadm command. + + Run doveadm command using the current user. Set :keyword:`sudo_user` if + you need different privileges. + + :param str params: the parameters to give to doveadm + :param str sudo_user: a valid system username + :param str pinput: data to send to process's stdin + :param bool capture_output: capture process output or not + :rtype: tuple + :return: return code, command output + """ + dpath = None + code, output = exec_cmd("which doveadm") + if not code: + dpath = force_text(output).strip() + else: + known_paths = getattr( + settings, "DOVEADM_LOOKUP_PATH", + ("/usr/bin/doveadm", "/usr/local/bin/doveadm") + ) + for fpath in known_paths: + if os.path.isfile(fpath) and os.access(fpath, os.X_OK): + dpath = fpath + break + if dpath: + return exec_cmd("{} {}".format(dpath, params), + sudo_user=sudo_user, + pinput=pinput, + capture_output=capture_output, + **kwargs) + else: + raise OSError("doveadm command not found") + + def guess_extension_name(): """Tries to guess the application's name by inspecting the stack. From c0e296ae9132fa37d1f48dcbf38b3fa0c24bc604 Mon Sep 17 00:00:00 2001 From: Anthony Bourguignon Date: Sun, 14 Oct 2018 11:09:04 +0200 Subject: [PATCH 5/9] add tests for password schemes --- modoboa/core/tests/test_authentication.py | 3 +++ modoboa/core/tests/test_views.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/modoboa/core/tests/test_authentication.py b/modoboa/core/tests/test_authentication.py index 509425503..29ed8e220 100644 --- a/modoboa/core/tests/test_authentication.py +++ b/modoboa/core/tests/test_authentication.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals +import os import smtplib from unittest import skipIf @@ -23,6 +24,8 @@ except ImportError: import mock +@override_settings( + DOVECOT_LOOKUP_PATH=["{}/doveadm".format(os.path.dirname(__file__))]) class AuthenticationTestCase(ModoTestCase): """Validate authentication scenarios.""" diff --git a/modoboa/core/tests/test_views.py b/modoboa/core/tests/test_views.py index 69d1c285e..20d730a0a 100644 --- a/modoboa/core/tests/test_views.py +++ b/modoboa/core/tests/test_views.py @@ -35,7 +35,7 @@ SETTINGS_SAMPLE = { "limits-enable_domain_limits": "False", "csrfmiddlewaretoken": "SGgMVZsA4TPqoiV786TMST6xgOlhAf4F", "limits-deflt_user_mailboxes_limit": "0", - "core-password_scheme": "sha512crypt", + "core-password_scheme": "plain", "core-update_scheme": True, "core-items_per_page": "30", "limits-deflt_user_mailbox_aliases_limit": "0", @@ -175,6 +175,15 @@ class SettingsTestCase(ModoTestCase): settings = SETTINGS_SAMPLE.copy() response = self.client.post(url, settings, format="json") self.assertEqual(response.status_code, 200) + settings["core-password_scheme"] = "sha512crypt" + response = self.client.post(url, settings, format="json") + self.assertEqual(response.status_code, 400) + compare(response.json(), { + "form_errors": {"password_scheme": ["Select a valid choice. \ +sha512crypt is not one of the available choices."]}, + "prefix": "core" + }) + settings["core-password_scheme"] = "plain" settings["core-rounds_number"] = "" response = self.client.post(url, settings, format="json") self.assertEqual(response.status_code, 400) From 6ebfee1f229e73c86e1fd7b397e34843ffc8d669 Mon Sep 17 00:00:00 2001 From: Anthony Bourguignon Date: Sun, 14 Oct 2018 11:10:02 +0200 Subject: [PATCH 6/9] add missing doveadm script --- modoboa/core/tests/doveadm | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 modoboa/core/tests/doveadm diff --git a/modoboa/core/tests/doveadm b/modoboa/core/tests/doveadm new file mode 100755 index 000000000..03474050b --- /dev/null +++ b/modoboa/core/tests/doveadm @@ -0,0 +1,3 @@ +#!/bin/sh +echo -n "MD5 MD5-CRYPT SHA SHA1 SHA256 SHA512 SMD5 SSHA SSHA256 SSHA512 PLAIN CLEAR CLEARTEXT PLAIN-TRUNC CRAM-MD5 SCRAM-SHA-1 HMAC-MD5 DIGEST-MD5 PLAIN-MD4 PLAIN-MD5 LDAP-MD5 LANMAN NTLM OTP SKEY RPA PBKDF2 CRYPT SHA256-CRYPT SHA512-CRYPT" +exit 0 From e2f7b47b47bf311d9db0328d0bc80c8c64af08cd Mon Sep 17 00:00:00 2001 From: Anthony Bourguignon Date: Sun, 14 Oct 2018 11:14:39 +0200 Subject: [PATCH 7/9] fix settings override in test_authentication --- modoboa/core/tests/test_authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa/core/tests/test_authentication.py b/modoboa/core/tests/test_authentication.py index 29ed8e220..8ac351f0b 100644 --- a/modoboa/core/tests/test_authentication.py +++ b/modoboa/core/tests/test_authentication.py @@ -25,7 +25,7 @@ except ImportError: @override_settings( - DOVECOT_LOOKUP_PATH=["{}/doveadm".format(os.path.dirname(__file__))]) + DOVEADM_LOOKUP_PATH=["{}/doveadm".format(os.path.dirname(__file__))]) class AuthenticationTestCase(ModoTestCase): """Validate authentication scenarios.""" From bb05f502cfbb707ea1e2450986a8ea4147f61a9d Mon Sep 17 00:00:00 2001 From: Anthony Bourguignon Date: Sun, 14 Oct 2018 12:41:59 +0200 Subject: [PATCH 8/9] Fix an issue with __metaclass__ in python3 improve code quality --- modoboa/core/password_hashers/__init__.py | 2 +- modoboa/core/password_hashers/base.py | 31 +++++++++++++++-------- modoboa/lib/sysutils.py | 3 ++- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/modoboa/core/password_hashers/__init__.py b/modoboa/core/password_hashers/__init__.py index 80a5a8529..661251784 100644 --- a/modoboa/core/password_hashers/__init__.py +++ b/modoboa/core/password_hashers/__init__.py @@ -36,7 +36,7 @@ def get_dovecot_schemes(): """Return schemes supported by dovecot""" supported_schemes = None try: - code, schemes = doveadm_cmd("pw -l") + _, schemes = doveadm_cmd("pw -l") except OSError: pass else: diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py index 88652de13..ccf2f9f2d 100644 --- a/modoboa/core/password_hashers/base.py +++ b/modoboa/core/password_hashers/base.py @@ -17,8 +17,27 @@ from random import Random from django.utils.crypto import constant_time_compare from django.utils.encoding import force_bytes, force_text +from six import with_metaclass -class PasswordHasher(object): + +class MetaHasher(type): + """ + PasswordHasher Metaclass + + Allow classmethod to be properties + """ + @property + def name(cls): + """Returns the name of the hasher""" + return cls.__name__.rstrip('Hasher').lower() + + @property + def label(cls): + """Returns the label of the hasher""" + return cls.name if not cls._weak else "{} (weak)".format(cls.name) + + +class PasswordHasher(with_metaclass(MetaHasher, object)): """ Base class of all hashers. """ @@ -27,16 +46,6 @@ class PasswordHasher(object): def __init__(self, target="local"): self._target = target - class __metaclass__(type): - """class properties""" - @property - def name(cls): - return cls.__name__.rstrip('Hasher').lower() - - @property - def label(cls): - return cls.name if not cls._weak else "{} (weak)".format(cls.name) - def _encrypt(self, clearvalue, salt=None): raise NotImplementedError diff --git a/modoboa/lib/sysutils.py b/modoboa/lib/sysutils.py index a363643fc..868eddcf8 100644 --- a/modoboa/lib/sysutils.py +++ b/modoboa/lib/sysutils.py @@ -46,7 +46,8 @@ def exec_cmd(cmd, sudo_user=None, pinput=None, capture_output=True, **kwargs): return process.returncode, output -def doveadm_cmd(params, sudo_user=None, pinput=None, capture_output=True, **kwargs): +def doveadm_cmd(params, sudo_user=None, pinput=None, + capture_output=True, **kwargs): """Execute doveadm command. Run doveadm command using the current user. Set :keyword:`sudo_user` if From 2c4d7073053f65f3f7b80e9c027b74c74ff6ecdf Mon Sep 17 00:00:00 2001 From: Anthony Bourguignon Date: Sun, 14 Oct 2018 12:50:32 +0200 Subject: [PATCH 9/9] Improve code quality (again) --- modoboa/core/password_hashers/base.py | 1 - modoboa/lib/sysutils.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py index ccf2f9f2d..ce05f4e72 100644 --- a/modoboa/core/password_hashers/base.py +++ b/modoboa/core/password_hashers/base.py @@ -23,7 +23,6 @@ from six import with_metaclass class MetaHasher(type): """ PasswordHasher Metaclass - Allow classmethod to be properties """ @property diff --git a/modoboa/lib/sysutils.py b/modoboa/lib/sysutils.py index 868eddcf8..eb0b8c92f 100644 --- a/modoboa/lib/sysutils.py +++ b/modoboa/lib/sysutils.py @@ -47,7 +47,7 @@ def exec_cmd(cmd, sudo_user=None, pinput=None, capture_output=True, **kwargs): def doveadm_cmd(params, sudo_user=None, pinput=None, - capture_output=True, **kwargs): + capture_output=True, **kwargs): """Execute doveadm command. Run doveadm command using the current user. Set :keyword:`sudo_user` if