From 2c534cbe7da8c467800d0ed02cd18482db42f6fb Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Mon, 4 Nov 2024 13:35:26 +0100 Subject: [PATCH 1/7] Updated test matrix --- .github/workflows/modoboa.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/modoboa.yml b/.github/workflows/modoboa.yml index eea0bdbab..fad858e79 100644 --- a/.github/workflows/modoboa.yml +++ b/.github/workflows/modoboa.yml @@ -72,7 +72,7 @@ jobs: strategy: matrix: database: ['postgres', 'mysql'] - python-version: [3.8, 3.9, '3.10', '3.11'] + python-version: [3.9, '3.10', '3.11', '3.12', '3.13'] fail-fast: false steps: @@ -109,7 +109,7 @@ jobs: pip install -r mysql-requirements.txt echo "DB=mysql" >> $GITHUB_ENV - name: Test with pytest - if: ${{ matrix.python-version != '3.11' || matrix.database != 'postgres' }} + if: ${{ matrix.python-version != '3.13' || matrix.database != 'postgres' }} run: | python ./tests.py cd test_project @@ -127,7 +127,7 @@ jobs: OIDC_RSA_PRIVATE_KEY: ${{ vars.OIDC_RSA_PRIVATE_KEY }} - name: Test with pytest and coverage - if: ${{ matrix.python-version == '3.11' && matrix.database == 'postgres' }} + if: ${{ matrix.python-version == '3.13' && matrix.database == 'postgres' }} run: | python ./tests.py cd test_project @@ -182,10 +182,10 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up Python 3.11 + - name: Set up Python 3.13 uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: '3.13' - name: Build frontend shell: bash -l {0} run: | From aa5d486a22db47020d12a0b22a03c475fdee0d49 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Mon, 4 Nov 2024 13:54:18 +0100 Subject: [PATCH 2/7] Try with newer lxml version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e6d86cba9..4ff57358a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ python-dateutil cryptography pytz requests -lxml==4.9.4 # FIXME: issue with version 5+ +lxml chardet ovh oath From e3c113849e0bccb6fd227ba07e8cd18c786d307a Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Mon, 4 Nov 2024 14:48:17 +0100 Subject: [PATCH 3/7] Fixed tests --- modoboa/lib/email_utils.py | 8 ++++---- modoboa/lib/tests/sample_messages/multipart-input.txt | 5 ++--- .../sample_messages/multipart-output-html_links.txt | 9 ++------- .../sample_messages/multipart-output-html_nolinks.txt | 9 ++------- .../sample_messages/multipart-output-plain_nolinks.txt | 4 ++-- modoboa/lib/tests/test_email_utils.py | 8 ++++---- requirements.txt | 2 +- 7 files changed, 17 insertions(+), 28 deletions(-) diff --git a/modoboa/lib/email_utils.py b/modoboa/lib/email_utils.py index 793304d8b..b216336bc 100644 --- a/modoboa/lib/email_utils.py +++ b/modoboa/lib/email_utils.py @@ -9,11 +9,11 @@ from email.utils import formataddr, formatdate, getaddresses, make_msgid import chardet import lxml.html from lxml.html import defs -from lxml.html.clean import Cleaner +from lxml_html_clean import Cleaner from django.core.mail import EmailMessage from django.template.loader import render_to_string -from django.utils.encoding import smart_str, smart_str +from django.utils.encoding import smart_str from django.utils.html import conditional_escape, escape from django.utils.translation import gettext as _ @@ -26,7 +26,7 @@ _RE_REMOVE_EXTRA_WHITESPACE = re.compile(r"\n\s*\n") _RE_CID = re.compile(r".*[ ]*cid=\"([^\"]*)\".*", re.I) -class EmailAddress(object): +class EmailAddress: def __init__(self, address): self.name, self.address = u2u_decode.decode_address(address) @@ -39,7 +39,7 @@ class EmailAddress(object): return self.fulladdress -class Email(object): +class Email: _basic_headers = ( "From", diff --git a/modoboa/lib/tests/sample_messages/multipart-input.txt b/modoboa/lib/tests/sample_messages/multipart-input.txt index ee0b51430..9b29082fd 100644 --- a/modoboa/lib/tests/sample_messages/multipart-input.txt +++ b/modoboa/lib/tests/sample_messages/multipart-input.txt @@ -57,7 +57,7 @@ This is an *html* /test /_message._ :-) --- +-- Someone someone@example.net @@ -83,12 +83,11 @@ Content-Transfer-Encoding: 7bit
-
-- 
+    
--
 someone
 someone@example.net
- --------------D585B241EEF60F0F44EC20E5 Content-Type: image/png; name="image.png" diff --git a/modoboa/lib/tests/sample_messages/multipart-output-html_links.txt b/modoboa/lib/tests/sample_messages/multipart-output-html_links.txt index 92c507ec5..67177a723 100644 --- a/modoboa/lib/tests/sample_messages/multipart-output-html_links.txt +++ b/modoboa/lib/tests/sample_messages/multipart-output-html_links.txt @@ -8,13 +8,8 @@ :-)


-
-- 
+    
--
 someone
 someone@example.net
-Some HTML text, a link, an -embedded image and an external image.
-
-
-Nifty!
- \ No newline at end of file + diff --git a/modoboa/lib/tests/sample_messages/multipart-output-html_nolinks.txt b/modoboa/lib/tests/sample_messages/multipart-output-html_nolinks.txt index 56d847fb8..00328ac8d 100644 --- a/modoboa/lib/tests/sample_messages/multipart-output-html_nolinks.txt +++ b/modoboa/lib/tests/sample_messages/multipart-output-html_nolinks.txt @@ -8,13 +8,8 @@ :-)


-
-- 
+    
--
 someone
 someone@example.net
-Some HTML text, a link, an -embedded image and an external image.
-
-
-Nifty!
- \ No newline at end of file + diff --git a/modoboa/lib/tests/sample_messages/multipart-output-plain_nolinks.txt b/modoboa/lib/tests/sample_messages/multipart-output-plain_nolinks.txt index 8795edadc..855fa5ca7 100644 --- a/modoboa/lib/tests/sample_messages/multipart-output-plain_nolinks.txt +++ b/modoboa/lib/tests/sample_messages/multipart-output-plain_nolinks.txt @@ -2,6 +2,6 @@ :-) --- +-- Someone -someone@example.net
\ No newline at end of file +someone@example.net
diff --git a/modoboa/lib/tests/test_email_utils.py b/modoboa/lib/tests/test_email_utils.py index 963ed319a..1c6844ccc 100644 --- a/modoboa/lib/tests/test_email_utils.py +++ b/modoboa/lib/tests/test_email_utils.py @@ -34,10 +34,10 @@ class EmailTests(SimpleTestCase): output: {message_id}-output-{dformat}_{no,}links.txt """ - def _get_expected_output(self, message_id, **kwargs): + def _get_expected_output(self, message_id: str, **kwargs) -> str: ext = kwargs["dformat"] if "dformat" in kwargs else "plain" ext += "_links" if "links" in kwargs and kwargs["links"] else "_nolinks" - message_path = os.path.join(SAMPLES_DIR, "%s-output-%s.txt" % (message_id, ext)) + message_path = os.path.join(SAMPLES_DIR, f"{message_id}-output-{ext}.txt") assert os.path.isfile(message_path), "%s does not exist." % message_path with open(message_path, "rb") as fp: @@ -46,11 +46,11 @@ class EmailTests(SimpleTestCase): return mail_text - def _test_email(self, message_id, **kwargs): + def _test_email(self, message_id: str, **kwargs) -> None: """Boiler plate code for testing e-mails.""" expected_output = self._get_expected_output(message_id, **kwargs) output = EmailTestImplementation(message_id, **kwargs).body - self.assertEqual(output, expected_output) + self.assertEqual(output.strip("\n"), expected_output.strip("\n")) def test_invalid_links_value(self): """modoboa-amavis set links = "0"; in python "0" == True, fixed in PR 79 diff --git a/requirements.txt b/requirements.txt index 4ff57358a..c314b0d8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ python-dateutil cryptography pytz requests -lxml +lxml[html_clean] chardet ovh oath From a076766f833d4c88722dae4bb6b0e9ff5ba23c7c Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 28 Nov 2024 15:55:04 +0100 Subject: [PATCH 4/7] First attempt to deal with password scheme deprecation. see #3350 --- modoboa/core/checks.py | 28 +++++++++++++++- modoboa/core/models.py | 15 +++++++-- modoboa/core/password_hashers/__init__.py | 2 +- modoboa/core/password_hashers/base.py | 40 ++++++++++++++++++----- modoboa/core/tests/test_authentication.py | 13 ++++++++ modoboa/core/tests/test_checks.py | 13 ++++++-- modoboa/lib/tests/__init__.py | 2 +- 7 files changed, 97 insertions(+), 16 deletions(-) diff --git a/modoboa/core/checks.py b/modoboa/core/checks.py index fb445a9e7..cf601a442 100644 --- a/modoboa/core/checks.py +++ b/modoboa/core/checks.py @@ -1,9 +1,18 @@ -from django.core.checks import register, Info, Warning +from django.core.checks import register, Error, Info, Warning from django.conf import settings from django.utils.translation import gettext as _ from modoboa.core.utils import generate_rsa_private_key +from modoboa.parameters.tools import get_global_parameter +from modoboa.core.password_hashers import get_password_hasher +EOO1 = Error( + _("The password scheme you use is not available anymore."), + hint=_( + "The password scheme you were using has been removed, please upgrade to a stronger one" + ), + id="modoboa.EOO1", +) W001 = Warning( _( @@ -14,6 +23,11 @@ W001 = Warning( hint=_("Set `USE_TZ = True` in settings.py"), id="modoboa.W001", ) +W002 = Warning( + _("The password scheme you are using is deprecated."), + hint=_("Upgrade your password scheme to a stronger one"), + id="modoboa.W002", +) @register(deploy=True) @@ -38,3 +52,15 @@ def check_rsa_private_key_exists(app_configs, **kwargs): if generate_rsa_private_key(settings.BASE_DIR): msgs.append(Info("An RSA private key has been generated for OIDC.")) return msgs + + +@register() +def check_password_hasher(app_configs, **kwargs): + msgs = [] + scheme_in_use = get_global_parameter("password_scheme", app="core") + hasher = get_password_hasher(scheme_in_use) + if not hasher.available: + msgs.append(EOO1) + elif hasher.deprecated: + msgs.append(W002) + return msgs diff --git a/modoboa/core/models.py b/modoboa/core/models.py index 25d3d4e0f..b947d8bce 100644 --- a/modoboa/core/models.py +++ b/modoboa/core/models.py @@ -2,6 +2,7 @@ import re from email.header import Header +from typing import Optional from django.core.serializers.json import DjangoJSONEncoder from django.conf import settings @@ -96,7 +97,7 @@ class User(AbstractUser): super().__init__(*args, **kwargs) self.parameters = param_tools.Manager("user", self._parameters) - def _crypt_password(self, raw_value): + def _crypt_password(self, raw_value: str) -> str: """Crypt the local password using the appropriate scheme. In case we don't find the scheme (for example when the @@ -115,9 +116,17 @@ class User(AbstractUser): "password_scheme", raise_exception=False ) raw_value = smart_bytes(raw_value) - return get_password_hasher(scheme.upper())().encrypt(raw_value) + hasher = get_password_hasher(scheme.upper())() + if not hasher.available: + raise InternalError( + _( + "The configured password scheme (%s) is not available anymore. Please update your configuration" + ) + % (scheme) + ) + return hasher.encrypt(raw_value) - def set_password(self, raw_value, curvalue=None): + def set_password(self, raw_value: str, curvalue: Optional[str] = None) -> None: """Password update. Update the current mailbox's password with the given clear diff --git a/modoboa/core/password_hashers/__init__.py b/modoboa/core/password_hashers/__init__.py index 5bc8bd086..560c43ef4 100644 --- a/modoboa/core/password_hashers/__init__.py +++ b/modoboa/core/password_hashers/__init__.py @@ -19,7 +19,7 @@ from modoboa.core.password_hashers.base import ( # NOQA:F401 ) -def get_password_hasher(scheme): +def get_password_hasher(scheme: str) -> PasswordHasher: """Retrieve the hasher corresponding to :keyword:`scheme`. If no class is found, `PLAINHasher` is returned. diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py index 0d355ac63..e56f2ba2e 100644 --- a/modoboa/core/password_hashers/base.py +++ b/modoboa/core/password_hashers/base.py @@ -5,10 +5,15 @@ Contains weak hashers (the original ones) available with Modoboa. """ import base64 -import crypt + +try: + import crypt +except ImportError: + crypt = None import hashlib -import string from random import Random +import string +from typing import Optional from django.utils.crypto import constant_time_compare from django.utils.encoding import force_bytes, force_str @@ -28,7 +33,15 @@ class MetaHasher(type): @property def label(cls): """Returns the label of the hasher""" - return cls.name if not cls._weak else "{} (weak)".format(cls.name) + result = cls.name + tags = [] + if cls._weak: + tags.append("weak") + if cls.deprecated: + tags.append("deprecated") + if tags: + result += f" ({', '.join(tags)})" + return result class PasswordHasher(metaclass=MetaHasher): @@ -36,12 +49,14 @@ class PasswordHasher(metaclass=MetaHasher): Base class of all hashers. """ - _weak = False + _weak: bool = False + deprecated: bool = False + scheme: str def __init__(self, target="local"): self._target = target - def _encrypt(self, clearvalue, salt=None): + def _encrypt(self, clearvalue: str, salt: Optional[str] = None) -> str: raise NotImplementedError def _b64encode(self, pwhash): @@ -54,7 +69,11 @@ class PasswordHasher(metaclass=MetaHasher): return base64.b64encode(pwhash) return pwhash - def encrypt(self, clearvalue): + @property + def available(self) -> bool: + return True + + def encrypt(self, clearvalue: str) -> str: """Encrypt a password. The format used to store passwords is the same than dovecot's one:: @@ -94,7 +113,7 @@ class PasswordHasher(metaclass=MetaHasher): @classmethod def get_password_hashers(cls): """Return all the PasswordHasher supported by Modoboa""" - return cls.__subclasses__() + return [hasher for hasher in cls.__subclasses__() if hasher().available] class PLAINHasher(PasswordHasher): @@ -120,12 +139,17 @@ class CRYPTHasher(PasswordHasher): """ _weak = True + deprecated = True + + @property + def available(self) -> bool: + return crypt is not None @property def scheme(self): return "{CRYPT}" - def _encrypt(self, clearvalue, salt=None): + def _encrypt(self, clearvalue: str, salt: Optional[str] = None) -> str: if salt is None: salt = "".join(Random().sample(string.ascii_letters + string.digits, 2)) return crypt.crypt(clearvalue, salt) diff --git a/modoboa/core/tests/test_authentication.py b/modoboa/core/tests/test_authentication.py index 9b0e4e306..7901fe37b 100644 --- a/modoboa/core/tests/test_authentication.py +++ b/modoboa/core/tests/test_authentication.py @@ -16,6 +16,7 @@ except ImportError: from modoboa.core import constants from modoboa.core.password_hashers import get_password_hasher from modoboa.core.password_hashers.utils import get_dovecot_schemes +from modoboa.lib.exceptions import InternalError from modoboa.lib.tests import NO_SMTP, ModoTestCase from .. import factories, models @@ -252,6 +253,18 @@ class AuthenticationTestCase(ModoTestCase): supported_schemes = get_dovecot_schemes()[0] self.assertEqual(supported_schemes, ["{MD5-CRYPT}", "{PLAIN}"]) + def test_unavailable_password_scheme(self): + self.set_global_parameter("password_scheme", "crypt") + hasher = get_password_hasher("crypt") + self.assertEqual(hasher.label, "crypt (weak, deprecated)") + if hasher().available: + return + username = "user@test.com" + password = "toto" + user = models.User.objects.get(username=username) + with self.assertRaises(InternalError): + user.set_password(password) + def test_fido_auth_begin(self): url = reverse("core:fido_auth_begin") resp = self.client.post(url) diff --git a/modoboa/core/tests/test_checks.py b/modoboa/core/tests/test_checks.py index b3beda5dd..dab27949b 100644 --- a/modoboa/core/tests/test_checks.py +++ b/modoboa/core/tests/test_checks.py @@ -2,13 +2,14 @@ import os import shutil import tempfile -from django.test import SimpleTestCase from django.test.utils import override_settings from modoboa.core import checks +from modoboa.lib.tests import SimpleModoTestCase +from modoboa.core.password_scheme.base import CRYPTHasher -class CheckSessionCookieSecureTest(SimpleTestCase): +class CheckSessionCookieSecureTest(SimpleModoTestCase): def setUp(self): self.workdir = tempfile.mkdtemp() @@ -35,3 +36,11 @@ class CheckSessionCookieSecureTest(SimpleTestCase): os.environ["OIDC_RSA_PRIVATE_KEY"] = "KEY" msgs = checks.check_rsa_private_key_exists(None) self.assertEqual(len(msgs), 0) + + def test_password_hasher_checks(self): + self.set_global_parameter("password_scheme", "crypt") + msgs = checks.check_password_hasher(None) + if CRYPTHasher().available: + self.assertEqual(msgs, [checks.W002]) + else: + self.assertEqual(msgs, [checks.E001]) diff --git a/modoboa/lib/tests/__init__.py b/modoboa/lib/tests/__init__.py index 1c5322920..23425ef51 100644 --- a/modoboa/lib/tests/__init__.py +++ b/modoboa/lib/tests/__init__.py @@ -30,7 +30,7 @@ except ImportError: NO_LDAP = True -class ParametersMixin(object): +class ParametersMixin: """Add tools to manage parameters.""" @classmethod From d3190cceeff4629104b096be4b2ad964badd9c69 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 28 Nov 2024 16:36:07 +0100 Subject: [PATCH 5/7] Postpone 3.13 support for now see #3350 --- .github/workflows/modoboa.yml | 10 +++++----- modoboa/core/checks.py | 14 ++------------ modoboa/core/models.py | 10 +--------- modoboa/core/password_hashers/base.py | 15 ++------------- modoboa/core/tests/test_authentication.py | 11 +---------- modoboa/core/tests/test_checks.py | 9 ++++----- 6 files changed, 15 insertions(+), 54 deletions(-) diff --git a/.github/workflows/modoboa.yml b/.github/workflows/modoboa.yml index fad858e79..2c65eadc4 100644 --- a/.github/workflows/modoboa.yml +++ b/.github/workflows/modoboa.yml @@ -72,7 +72,7 @@ jobs: strategy: matrix: database: ['postgres', 'mysql'] - python-version: [3.9, '3.10', '3.11', '3.12', '3.13'] + python-version: [3.9, '3.10', '3.11', '3.12'] fail-fast: false steps: @@ -109,7 +109,7 @@ jobs: pip install -r mysql-requirements.txt echo "DB=mysql" >> $GITHUB_ENV - name: Test with pytest - if: ${{ matrix.python-version != '3.13' || matrix.database != 'postgres' }} + if: ${{ matrix.python-version != '3.12' || matrix.database != 'postgres' }} run: | python ./tests.py cd test_project @@ -127,7 +127,7 @@ jobs: OIDC_RSA_PRIVATE_KEY: ${{ vars.OIDC_RSA_PRIVATE_KEY }} - name: Test with pytest and coverage - if: ${{ matrix.python-version == '3.13' && matrix.database == 'postgres' }} + if: ${{ matrix.python-version == '3.12' && matrix.database == 'postgres' }} run: | python ./tests.py cd test_project @@ -182,10 +182,10 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up Python 3.13 + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: - python-version: '3.13' + python-version: '3.12' - name: Build frontend shell: bash -l {0} run: | diff --git a/modoboa/core/checks.py b/modoboa/core/checks.py index cf601a442..a8977339f 100644 --- a/modoboa/core/checks.py +++ b/modoboa/core/checks.py @@ -1,4 +1,4 @@ -from django.core.checks import register, Error, Info, Warning +from django.core.checks import register, Info, Warning from django.conf import settings from django.utils.translation import gettext as _ @@ -6,14 +6,6 @@ from modoboa.core.utils import generate_rsa_private_key from modoboa.parameters.tools import get_global_parameter from modoboa.core.password_hashers import get_password_hasher -EOO1 = Error( - _("The password scheme you use is not available anymore."), - hint=_( - "The password scheme you were using has been removed, please upgrade to a stronger one" - ), - id="modoboa.EOO1", -) - W001 = Warning( _( "You have USE_TZ set to False, this may result in issues during " @@ -59,8 +51,6 @@ def check_password_hasher(app_configs, **kwargs): msgs = [] scheme_in_use = get_global_parameter("password_scheme", app="core") hasher = get_password_hasher(scheme_in_use) - if not hasher.available: - msgs.append(EOO1) - elif hasher.deprecated: + if hasher.deprecated: msgs.append(W002) return msgs diff --git a/modoboa/core/models.py b/modoboa/core/models.py index b947d8bce..a9595c719 100644 --- a/modoboa/core/models.py +++ b/modoboa/core/models.py @@ -116,15 +116,7 @@ class User(AbstractUser): "password_scheme", raise_exception=False ) raw_value = smart_bytes(raw_value) - hasher = get_password_hasher(scheme.upper())() - if not hasher.available: - raise InternalError( - _( - "The configured password scheme (%s) is not available anymore. Please update your configuration" - ) - % (scheme) - ) - return hasher.encrypt(raw_value) + return get_password_hasher(scheme.upper())().encrypt(raw_value) def set_password(self, raw_value: str, curvalue: Optional[str] = None) -> None: """Password update. diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py index e56f2ba2e..f6346ca73 100644 --- a/modoboa/core/password_hashers/base.py +++ b/modoboa/core/password_hashers/base.py @@ -6,10 +6,7 @@ Contains weak hashers (the original ones) available with Modoboa. import base64 -try: - import crypt -except ImportError: - crypt = None +import crypt import hashlib from random import Random import string @@ -69,10 +66,6 @@ class PasswordHasher(metaclass=MetaHasher): return base64.b64encode(pwhash) return pwhash - @property - def available(self) -> bool: - return True - def encrypt(self, clearvalue: str) -> str: """Encrypt a password. @@ -113,7 +106,7 @@ class PasswordHasher(metaclass=MetaHasher): @classmethod def get_password_hashers(cls): """Return all the PasswordHasher supported by Modoboa""" - return [hasher for hasher in cls.__subclasses__() if hasher().available] + return [hasher for hasher in cls.__subclasses__()] class PLAINHasher(PasswordHasher): @@ -141,10 +134,6 @@ class CRYPTHasher(PasswordHasher): _weak = True deprecated = True - @property - def available(self) -> bool: - return crypt is not None - @property def scheme(self): return "{CRYPT}" diff --git a/modoboa/core/tests/test_authentication.py b/modoboa/core/tests/test_authentication.py index 7901fe37b..58acb51d6 100644 --- a/modoboa/core/tests/test_authentication.py +++ b/modoboa/core/tests/test_authentication.py @@ -16,7 +16,6 @@ except ImportError: from modoboa.core import constants from modoboa.core.password_hashers import get_password_hasher from modoboa.core.password_hashers.utils import get_dovecot_schemes -from modoboa.lib.exceptions import InternalError from modoboa.lib.tests import NO_SMTP, ModoTestCase from .. import factories, models @@ -253,17 +252,9 @@ class AuthenticationTestCase(ModoTestCase): supported_schemes = get_dovecot_schemes()[0] self.assertEqual(supported_schemes, ["{MD5-CRYPT}", "{PLAIN}"]) - def test_unavailable_password_scheme(self): - self.set_global_parameter("password_scheme", "crypt") + def test_deprecated_password_scheme(self): hasher = get_password_hasher("crypt") self.assertEqual(hasher.label, "crypt (weak, deprecated)") - if hasher().available: - return - username = "user@test.com" - password = "toto" - user = models.User.objects.get(username=username) - with self.assertRaises(InternalError): - user.set_password(password) def test_fido_auth_begin(self): url = reverse("core:fido_auth_begin") diff --git a/modoboa/core/tests/test_checks.py b/modoboa/core/tests/test_checks.py index dab27949b..c8aa40c54 100644 --- a/modoboa/core/tests/test_checks.py +++ b/modoboa/core/tests/test_checks.py @@ -6,7 +6,7 @@ from django.test.utils import override_settings from modoboa.core import checks from modoboa.lib.tests import SimpleModoTestCase -from modoboa.core.password_scheme.base import CRYPTHasher +from modoboa.core.password_hashers.base import CRYPTHasher class CheckSessionCookieSecureTest(SimpleModoTestCase): @@ -38,9 +38,8 @@ class CheckSessionCookieSecureTest(SimpleModoTestCase): self.assertEqual(len(msgs), 0) def test_password_hasher_checks(self): + msgs = checks.check_password_hasher(None) + self.assertEqual(msgs, []) self.set_global_parameter("password_scheme", "crypt") msgs = checks.check_password_hasher(None) - if CRYPTHasher().available: - self.assertEqual(msgs, [checks.W002]) - else: - self.assertEqual(msgs, [checks.E001]) + self.assertEqual(msgs, [checks.E001]) From 760a4bbf76e45b89e29516eb29a1665b19261ea2 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 28 Nov 2024 16:42:41 +0100 Subject: [PATCH 6/7] Fixed wrong constant name --- modoboa/core/tests/test_checks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa/core/tests/test_checks.py b/modoboa/core/tests/test_checks.py index c8aa40c54..0c538b865 100644 --- a/modoboa/core/tests/test_checks.py +++ b/modoboa/core/tests/test_checks.py @@ -42,4 +42,4 @@ class CheckSessionCookieSecureTest(SimpleModoTestCase): self.assertEqual(msgs, []) self.set_global_parameter("password_scheme", "crypt") msgs = checks.check_password_hasher(None) - self.assertEqual(msgs, [checks.E001]) + self.assertEqual(msgs, [checks.W002]) From dcefab701e8d4ca732f854b22f1d2a848bdb1892 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 28 Nov 2024 16:52:24 +0100 Subject: [PATCH 7/7] Fixed workflow --- .github/workflows/modoboa.yml | 2 +- docker/Dockerfile.dev | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/modoboa.yml b/.github/workflows/modoboa.yml index 2c65eadc4..893468a62 100644 --- a/.github/workflows/modoboa.yml +++ b/.github/workflows/modoboa.yml @@ -148,7 +148,7 @@ jobs: OIDC_RSA_PRIVATE_KEY: ${{ vars.OIDC_RSA_PRIVATE_KEY }} - name: Upload coverage result - if: ${{ matrix.python-version == '3.11' && matrix.database == 'postgres' }} + if: ${{ matrix.python-version == '3.12' && matrix.database == 'postgres' }} uses: actions/upload-artifact@v4 with: name: coverage-results diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index cd965d33d..536ff01ec 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -1,10 +1,10 @@ -FROM python:3.12-alpine as base +FROM python:3.12-alpine AS base -MAINTAINER Antoine Nguyen +LABEL org.opencontainers.image.authors="Antoine Nguyen " ENV VIRTUAL_ENV=/opt/venv ENV PATH="$VIRTUAL_ENV/bin:$PATH" -FROM base as build +FROM base AS build RUN apk add --update openssl librrd python3-dev libffi-dev gcc g++ musl-dev libxml2-dev libxslt-dev \ libressl-dev jpeg-dev rrdtool-dev file make gettext freetype-dev cairo-dev cargo \ @@ -16,7 +16,8 @@ COPY test-requirements.txt /tmp COPY dev-requirements.txt /tmp RUN python -m pip install -U pip RUN pip install -r requirements.txt -r test-requirements.txt -r dev-requirements.txt -FROM base as run + +FROM base AS run COPY docker/doveadm /usr/local/bin RUN chmod +x /usr/local/bin/doveadm COPY --from=build $VIRTUAL_ENV $VIRTUAL_ENV