From 27dbd71a6057b380c4a355075dfc3cea995b9e21 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Fri, 29 Nov 2024 17:04:48 +0100 Subject: [PATCH] Fixed wrong way to check for passwords using deprecated scheme. see #3350 --- doc/upgrade.rst | 2 +- frontend/src/views/admin/InformationView.vue | 20 +++++++++++++++++++- modoboa/core/checks.py | 11 ++++++----- modoboa/core/handlers.py | 8 ++++---- modoboa/core/models.py | 2 +- modoboa/core/password_hashers/__init__.py | 4 ++-- modoboa/core/password_hashers/base.py | 7 ++++++- modoboa/core/templates/core/information.html | 3 ++- modoboa/core/utils.py | 19 +++++++++++++++++-- modoboa/core/views/admin.py | 11 ++++------- 10 files changed, 62 insertions(+), 25 deletions(-) diff --git a/doc/upgrade.rst b/doc/upgrade.rst index 83bf21de0..d269ac512 100644 --- a/doc/upgrade.rst +++ b/doc/upgrade.rst @@ -168,7 +168,7 @@ Update your :file:`settings.py` file with following content: "ckeditor.W001", # CKEditor 4.22.1 warning ] -This will silence a `warning sent by django-ckeditor _`. +This will silence a `warning sent by django-ckeditor `_. Version 2.3.0 ============= diff --git a/frontend/src/views/admin/InformationView.vue b/frontend/src/views/admin/InformationView.vue index a8aca5404..21304353a 100644 --- a/frontend/src/views/admin/InformationView.vue +++ b/frontend/src/views/admin/InformationView.vue @@ -23,7 +23,7 @@ 'The password scheme you are using has been deprecated and will be removed in the next minor version. The procedure to upgrade to a stronger scheme is as follows:' ) }} -
    +
    +

    +

    diff --git a/modoboa/core/checks.py b/modoboa/core/checks.py index 79c229c93..e487761b0 100644 --- a/modoboa/core/checks.py +++ b/modoboa/core/checks.py @@ -2,9 +2,10 @@ from django.core.checks import register, Info, Warning from django.conf import settings from django.utils.translation import gettext as _ -from modoboa.core.models import User -from modoboa.core.password_hashers import get_configured_password_hasher -from modoboa.core.utils import generate_rsa_private_key +from modoboa.core.utils import ( + check_for_deprecated_password_schemes, + generate_rsa_private_key, +) W001 = Warning( _( @@ -49,7 +50,7 @@ def check_rsa_private_key_exists(app_configs, **kwargs): @register() def check_password_hasher(app_configs, **kwargs): msgs = [] - hasher = get_configured_password_hasher() - if hasher.deprecated or User.objects.is_password_scheme_in_use(hasher): + hasher = check_for_deprecated_password_schemes() + if hasher: msgs.append(W002) return msgs diff --git a/modoboa/core/handlers.py b/modoboa/core/handlers.py index ac8334c27..649716bd8 100644 --- a/modoboa/core/handlers.py +++ b/modoboa/core/handlers.py @@ -12,7 +12,7 @@ from django.urls import reverse from django.utils.encoding import smart_str from django.utils.translation import gettext as _ -from modoboa.core.password_hashers import get_configured_password_hasher +from modoboa.core.utils import check_for_deprecated_password_schemes from modoboa.lib import exceptions, permissions from modoboa.lib.signals import get_request from . import models, signals as core_signals, utils @@ -132,13 +132,13 @@ def check_for_new_versions(sender, include_all: bool, **kwargs) -> list: ] elif include_all: result += [{"id": "newversionavailable"}] - hasher = get_configured_password_hasher() - if hasher.deprecated or models.User.objects.is_password_scheme_in_use(hasher): + hasher = check_for_deprecated_password_schemes() + if hasher: result += [ { "id": "deprecatedpasswordscheme", "url": reverse("core:index") + "#info/", - "text": _("You are using a deprecated password scheme (%s)") + "text": _("You are still using a deprecated password scheme (%s)") % hasher.name, "level": "warning", } diff --git a/modoboa/core/models.py b/modoboa/core/models.py index 906f3d3d2..8f35eb108 100644 --- a/modoboa/core/models.py +++ b/modoboa/core/models.py @@ -44,7 +44,7 @@ def get_default_language() -> str: class UserManager(BaseUserManager): """Custom manager for User.""" - def is_password_scheme_in_use(self, password_hasher: PasswordHasher) -> bool: + def is_password_scheme_in_use(self, password_hasher: type[PasswordHasher]) -> bool: """Check if given password scheme is still in use.""" return ( self.get_queryset() diff --git a/modoboa/core/password_hashers/__init__.py b/modoboa/core/password_hashers/__init__.py index 7f644d10d..6127447d0 100644 --- a/modoboa/core/password_hashers/__init__.py +++ b/modoboa/core/password_hashers/__init__.py @@ -18,7 +18,7 @@ from modoboa.core.password_hashers.base import ( # NOQA:F401 from modoboa.parameters import tools as param_tools -def get_password_hasher(scheme: str) -> PasswordHasher: +def get_password_hasher(scheme: str) -> type[PasswordHasher]: """Retrieve the hasher corresponding to :keyword:`scheme`. If no class is found, `PLAINHasher` is returned. @@ -35,7 +35,7 @@ def get_password_hasher(scheme: str) -> PasswordHasher: return hasher -def get_configured_password_hasher() -> PasswordHasher: +def get_configured_password_hasher() -> type[PasswordHasher]: """Retrieve the password hasher class currently configured.""" scheme = param_tools.get_global_parameter("password_scheme") return get_password_hasher(scheme) diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py index f6346ca73..e1ff5056d 100644 --- a/modoboa/core/password_hashers/base.py +++ b/modoboa/core/password_hashers/base.py @@ -104,10 +104,15 @@ class PasswordHasher(metaclass=MetaHasher): return False @classmethod - def get_password_hashers(cls): + def get_password_hashers(cls) -> list[type["PasswordHasher"]]: """Return all the PasswordHasher supported by Modoboa""" return [hasher for hasher in cls.__subclasses__()] + @classmethod + def get_deprecated_password_hashers(cls) -> list[type["PasswordHasher"]]: + """Return all the PasswordHasher supported by Modoboa and marked as deprecated.""" + return [hasher for hasher in cls.__subclasses__() if hasher.deprecated] + class PLAINHasher(PasswordHasher): """ diff --git a/modoboa/core/templates/core/information.html b/modoboa/core/templates/core/information.html index 1b2d96d82..7d79413ae 100644 --- a/modoboa/core/templates/core/information.html +++ b/modoboa/core/templates/core/information.html @@ -1,6 +1,6 @@ {% load i18n core_tags lib_tags %}

    {% trans "Information" %} {% trans "Various information about Modoboa" %}


    -{% if password_hasher.deprecated or password_hasher_in_use %} +{% if password_hasher %}

    {% trans "You are using a deprecated password scheme!" %}

    {% blocktranslate trimmed %} @@ -20,6 +20,7 @@ version of Modoboa, otherwise you will be unable to connect to the web interface anymore. {% endblocktranslate %}

    +

    {% blocktranslate trimmed %}Please note that you will see this message until ALL user passwords have been converted using the new scheme.{% endblocktranslate %}

    {% endif %} {% if update_avail %} diff --git a/modoboa/core/utils.py b/modoboa/core/utils.py index f096b3d63..a1987298e 100644 --- a/modoboa/core/utils.py +++ b/modoboa/core/utils.py @@ -1,15 +1,18 @@ """Utility functions.""" -from pkg_resources import parse_version -import os import environ +import os +from pkg_resources import parse_version +from typing import Optional from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa from django.utils.translation import gettext as _ +from modoboa.core import models from modoboa.core.extensions import exts_pool +from modoboa.core.password_hashers import get_configured_password_hasher, PasswordHasher from modoboa.lib.api_client import ModoAPIClient @@ -85,3 +88,15 @@ def generate_rsa_private_key(storage_path: str) -> bool: with open(env_path, "wb") as fp: fp.write(b'OIDC_RSA_PRIVATE_KEY="' + content + b'"\n') return True + + +def check_for_deprecated_password_schemes() -> Optional[type[PasswordHasher]]: + """Check if deprecated password scheme is still in use.""" + hasher = get_configured_password_hasher() + if hasher.deprecated: + return hasher + deprecated_hashers = PasswordHasher.get_deprecated_password_hashers() + for dhasher in deprecated_hashers: + if models.User.objects.is_password_scheme_in_use(dhasher): + return dhasher + return None diff --git a/modoboa/core/views/admin.py b/modoboa/core/views/admin.py index 64559744e..a2fe6b3df 100644 --- a/modoboa/core/views/admin.py +++ b/modoboa/core/views/admin.py @@ -7,9 +7,8 @@ from django.shortcuts import render from django.template.loader import render_to_string from django.utils.translation import gettext as _ -from modoboa.core.models import Log, User -from modoboa.core.password_hashers import get_configured_password_hasher -from modoboa.core.utils import check_for_updates +from modoboa.core.models import Log +from modoboa.core.utils import check_for_updates, check_for_deprecated_password_schemes from modoboa.lib.listing import get_listing_page, get_sort_order from modoboa.lib.web_utils import render_to_json_response from modoboa.parameters import tools as param_tools @@ -61,8 +60,7 @@ def parameters(request, tplname="core/parameters.html"): @user_passes_test(lambda u: u.is_superuser) def information(request, tplname="core/information.html"): status, extensions = check_for_updates() - hasher = get_configured_password_hasher() - password_hasher_in_use = User.objects.is_password_scheme_in_use(hasher) + hasher = check_for_deprecated_password_schemes() return render_to_json_response( { "content": render_to_string( @@ -71,8 +69,7 @@ def information(request, tplname="core/information.html"): "update_avail": status, "extensions": extensions, "password_hasher": hasher, - "password_hasher_in_use": password_hasher_in_use, - "scheme": hasher.name, + "scheme": hasher.name if hasher else "", }, ), }