From a53aaddce3d895df6e26b58622b5ef77633d0031 Mon Sep 17 00:00:00 2001 From: Spitap Date: Sat, 25 Feb 2023 16:12:51 +0100 Subject: [PATCH 1/7] Added ability to disable inactive accounts --- modoboa/core/app_settings.py | 5 +++-- .../management/commands/clean_inactive_accounts.py | 2 ++ modoboa/core/tests/test_core.py | 12 ++++++++++-- modoboa/lib/tests/__init__.py | 3 +++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index 7b4aaeee1..656b0ffbd 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -462,7 +462,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm): initial=30, help_text=_( "An account with a last login date greater than this threshold " - "(in days) will be considered as inactive" + "(in days) will be considered as inactive. Disabled if the number is negative." ), ) @@ -1076,7 +1076,8 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict([ "label": ugettext_lazy("Inactive account threshold"), "help_text": ugettext_lazy( "An account with a last login date greater than this " - "threshold (in days) will be considered as inactive" + "threshold (in days) will be considered as inactive. " + "Disabled if the number is negative." ) }), ("top_notifications_check_interval", { diff --git a/modoboa/core/management/commands/clean_inactive_accounts.py b/modoboa/core/management/commands/clean_inactive_accounts.py index eb5e58778..23cd06e3a 100644 --- a/modoboa/core/management/commands/clean_inactive_accounts.py +++ b/modoboa/core/management/commands/clean_inactive_accounts.py @@ -45,6 +45,8 @@ class Command(BaseCommand): """Command entry point.""" inactive_account_threshold = param_tools.get_global_parameter( "inactive_account_threshold") + if inactive_account_threshold < 0: + return qset = models.User.objects.filter( is_active=True, is_superuser=False, last_login__lt=timezone.now() - diff --git a/modoboa/core/tests/test_core.py b/modoboa/core/tests/test_core.py index b0b476bc2..a738918d5 100644 --- a/modoboa/core/tests/test_core.py +++ b/modoboa/core/tests/test_core.py @@ -11,7 +11,7 @@ from django.test import TestCase from django.urls import reverse from django.utils import timezone -from modoboa.lib.tests import ModoTestCase +from modoboa.lib.tests import ModoTestCase, SimpleModoTestCase from .. import factories, mocks, models @@ -44,7 +44,7 @@ class AuthenticationTestCase(ModoTestCase): self.assertTrue(response.url.endswith(reverse("core:dashboard"))) -class ManagementCommandsTestCase(TestCase): +class ManagementCommandsTestCase(SimpleModoTestCase): """Test management commands.""" def test_change_default_admin(self): @@ -84,6 +84,14 @@ class ManagementCommandsTestCase(TestCase): "clean_inactive_accounts", "--verbose", "--dry-run", stdout=out) self.assertIn("user1@domain.test", out.getvalue()) + # Set a negative number (means disabled) + self.set_global_parameter("inactive_account_threshold", -1) + out = StringIO() + management.call_command( + "clean_inactive_accounts", "--verbose", "--dry-run", stdout=out) + self.assertEqual("", out.getvalue()) + + self.set_global_parameter("inactive_account_threshold", 30) management.call_command("clean_inactive_accounts", "--silent") account.refresh_from_db() self.assertFalse(account.is_active) diff --git a/modoboa/lib/tests/__init__.py b/modoboa/lib/tests/__init__.py index 608197a41..06cc1862f 100644 --- a/modoboa/lib/tests/__init__.py +++ b/modoboa/lib/tests/__init__.py @@ -51,6 +51,9 @@ class ParametersMixin(object): self.localconfig.parameters.set_values(parameters, app=app) self.localconfig.save() +class SimpleModoTestCase(ParametersMixin, TestCase): + """Simple class to add parameters editing.""" + class ModoTestCase(ParametersMixin, TestCase): """All test cases must inherit from this one.""" From 2aea37db226c76725ae851e7c88da8ff7765de90 Mon Sep 17 00:00:00 2001 From: Spitap Date: Sun, 26 Feb 2023 19:04:04 +0100 Subject: [PATCH 2/7] Handle disabling by a flag --- .../components/parameters/ParametersForm.vue | 1 + modoboa/core/api/v2/serializers.py | 1 + modoboa/core/api/v2/tests.py | 1 + modoboa/core/app_settings.py | 19 +++++++++++++++++-- .../commands/clean_inactive_accounts.py | 8 ++++++-- modoboa/core/tests/test_core.py | 11 ++++++----- modoboa/parameters/tools.py | 2 +- 7 files changed, 33 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/parameters/ParametersForm.vue b/frontend/src/components/parameters/ParametersForm.vue index 15b4ed460..bc08e8d7b 100644 --- a/frontend/src/components/parameters/ParametersForm.vue +++ b/frontend/src/components/parameters/ParametersForm.vue @@ -133,6 +133,7 @@ export default { return params.filter(param => this.display(param)) }, save () { + this.formErrors = {} parameters.saveApplication(this.$route.params.app, this.parameters).then(() => { bus.$emit('notification', { msg: this.$gettext('Parameters updated') }) }).catch(error => { diff --git a/modoboa/core/api/v2/serializers.py b/modoboa/core/api/v2/serializers.py index 7afaff798..f766fd6ce 100644 --- a/modoboa/core/api/v2/serializers.py +++ b/modoboa/core/api/v2/serializers.py @@ -159,6 +159,7 @@ class CoreGlobalParametersSerializer(serializers.Serializer): send_statistics = serializers.BooleanField(default=True) # Misc settings + enable_inactive_accounts = serializers.BooleanField(default=True) inactive_account_threshold = serializers.IntegerField(default=30) top_notifications_check_interval = serializers.IntegerField(default=30) log_maximum_age = serializers.IntegerField(default=365) diff --git a/modoboa/core/api/v2/tests.py b/modoboa/core/api/v2/tests.py index 4caae77f7..079437e92 100644 --- a/modoboa/core/api/v2/tests.py +++ b/modoboa/core/api/v2/tests.py @@ -59,6 +59,7 @@ CORE_SETTINGS = { "send_new_versions_email": False, "new_versions_email_rcpt": "postmaster@yourdomain.test", "send_statistics": True, + "enable_inactive_accounts": True, "inactive_account_threshold": 30, "top_notifications_check_interval": 30, "log_maximum_age": 365, diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index 656b0ffbd..a30b85772 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -457,12 +457,19 @@ class GeneralParametersForm(param_forms.AdminParametersForm): sep3 = SeparatorField(label=ugettext_lazy("Miscellaneous")) + enable_inactive_accounts = YesNoField( + label=_("Enable inactive account tracking"), + initial=True, + help_text=_("Allow the administrator to set a threshold beyond which " + "an account is considered inactive if the user hasn't logged.") + ) + inactive_account_threshold = forms.IntegerField( label=_("Inactive account threshold"), initial=30, help_text=_( "An account with a last login date greater than this threshold " - "(in days) will be considered as inactive. Disabled if the number is negative." + "(in days) will be considered as inactive." ), ) @@ -520,6 +527,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm): "send_new_versions_email": "check_new_versions=True", "new_versions_email_rcpt": "check_new_versions=True", "sms_provider": "sms_password_recovery=True", + "inactive_account_threshold": "enable_inactive_accounts=True" } def __init__(self, *args, **kwargs): @@ -1072,12 +1080,19 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict([ ("misc", { "label": ugettext_lazy("Miscellaneous"), "params": collections.OrderedDict([ + ("enable_inactive_accounts", { + "label": ugettext_lazy("Enable inactive account tracking"), + "help_text": ugettext_lazy( + "Allow the administrator to set a threshold beyond which " + "an account is considered inactive if the user hasn't logged." + ), + }), ("inactive_account_threshold", { "label": ugettext_lazy("Inactive account threshold"), + "display": "enable_inactive_accounts=true", "help_text": ugettext_lazy( "An account with a last login date greater than this " "threshold (in days) will be considered as inactive. " - "Disabled if the number is negative." ) }), ("top_notifications_check_interval", { diff --git a/modoboa/core/management/commands/clean_inactive_accounts.py b/modoboa/core/management/commands/clean_inactive_accounts.py index 23cd06e3a..2c48e4754 100644 --- a/modoboa/core/management/commands/clean_inactive_accounts.py +++ b/modoboa/core/management/commands/clean_inactive_accounts.py @@ -43,10 +43,14 @@ class Command(BaseCommand): def handle(self, *args, **options): """Command entry point.""" + if not param_tools.get_global_parameter( + "enable_inactive_accounts"): + if options["verbose"]: + print("Inactive accounts detection is disabled.", + file=self.stdout) + return inactive_account_threshold = param_tools.get_global_parameter( "inactive_account_threshold") - if inactive_account_threshold < 0: - return qset = models.User.objects.filter( is_active=True, is_superuser=False, last_login__lt=timezone.now() - diff --git a/modoboa/core/tests/test_core.py b/modoboa/core/tests/test_core.py index a738918d5..ef7b99688 100644 --- a/modoboa/core/tests/test_core.py +++ b/modoboa/core/tests/test_core.py @@ -84,14 +84,15 @@ class ManagementCommandsTestCase(SimpleModoTestCase): "clean_inactive_accounts", "--verbose", "--dry-run", stdout=out) self.assertIn("user1@domain.test", out.getvalue()) - # Set a negative number (means disabled) - self.set_global_parameter("inactive_account_threshold", -1) + # Disable account account threshold + self.set_global_parameter("enable_inactive_accounts", False) out = StringIO() management.call_command( - "clean_inactive_accounts", "--verbose", "--dry-run", stdout=out) - self.assertEqual("", out.getvalue()) + "clean_inactive_accounts", "--verbose", stdout=out) + self.assertIn("Inactive accounts detection is disabled.", + out.getvalue()) - self.set_global_parameter("inactive_account_threshold", 30) + self.set_global_parameter("enable_inactive_accounts", True) management.call_command("clean_inactive_accounts", "--silent") account.refresh_from_db() self.assertFalse(account.is_active) diff --git a/modoboa/parameters/tools.py b/modoboa/parameters/tools.py index cda90ba1e..54e3eaff5 100644 --- a/modoboa/parameters/tools.py +++ b/modoboa/parameters/tools.py @@ -63,7 +63,7 @@ class Registry(object): result = [{"name": key, "label": value["label"]} for key, value in self._registry2[level].items()] return result - + def get_label(self, level, app): return self._registry2[level][app]["label"] From 1f6ac324fd9e3db03c9345ebe89eb76639a81d4c Mon Sep 17 00:00:00 2001 From: Spitap Date: Sun, 26 Feb 2023 19:18:25 +0100 Subject: [PATCH 3/7] Fixed tests --- modoboa/core/app_settings.py | 4 ++-- modoboa/core/tests/test_views.py | 1 + modoboa/dmarc/management/commands/import_aggregated_report.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index a30b85772..38b711191 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -469,7 +469,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm): initial=30, help_text=_( "An account with a last login date greater than this threshold " - "(in days) will be considered as inactive." + "(in days) will be considered as inactive" ), ) @@ -1092,7 +1092,7 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict([ "display": "enable_inactive_accounts=true", "help_text": ugettext_lazy( "An account with a last login date greater than this " - "threshold (in days) will be considered as inactive. " + "threshold (in days) will be considered as inactive" ) }), ("top_notifications_check_interval", { diff --git a/modoboa/core/tests/test_views.py b/modoboa/core/tests/test_views.py index 488549915..bbd476391 100644 --- a/modoboa/core/tests/test_views.py +++ b/modoboa/core/tests/test_views.py @@ -60,6 +60,7 @@ SETTINGS_SAMPLE = { "core-default_password": "Toto1000", "limits-deflt_user_quota_limit": "0", "core-hide_features_widget": "False", + "core-enable_inactive_accounts": "True", "core-inactive_account_threshold": "30", "admin-domains_must_have_authorized_mx": False, "core-random_password_length": "8", diff --git a/modoboa/dmarc/management/commands/import_aggregated_report.py b/modoboa/dmarc/management/commands/import_aggregated_report.py index 28d211ee9..ec1d2b582 100644 --- a/modoboa/dmarc/management/commands/import_aggregated_report.py +++ b/modoboa/dmarc/management/commands/import_aggregated_report.py @@ -37,4 +37,4 @@ class Command(BaseCommand): elif options.get("imap"): lib.import_from_imap(options) else: - print("Nothing to do.") + print("Nothing to do.", file=self.stdout) From ac1d6ac1cce9a2b890b75ec38056738aebbb7024 Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 27 Feb 2023 10:14:01 +0100 Subject: [PATCH 4/7] Added throttle to pdfcredentials view --- modoboa/pdfcredentials/api/v2/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modoboa/pdfcredentials/api/v2/views.py b/modoboa/pdfcredentials/api/v2/views.py index d92955ac4..5d2345fce 100644 --- a/modoboa/pdfcredentials/api/v2/views.py +++ b/modoboa/pdfcredentials/api/v2/views.py @@ -9,6 +9,7 @@ from rest_framework import permissions from rest_framework.views import APIView from modoboa.admin.models import Domain +from modoboa.lib.throttle import UserLesserDdosUser from .serializers import GetAccountCredentialsSerializer @@ -22,6 +23,7 @@ class PDFCredentialView(APIView): ) serializer_class = GetAccountCredentialsSerializer + throttle_classes = [UserLesserDdosUser] def get_queryset(self): """Filter queryset based on current user.""" From 7c9ad9a4ecf8bc8cd5fb6fe398f4755c2f0cd11a Mon Sep 17 00:00:00 2001 From: Spitap Date: Fri, 3 Mar 2023 09:38:50 +0100 Subject: [PATCH 5/7] Specify that threshold is in days --- modoboa/core/app_settings.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index 30536366a..811c2df62 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -458,8 +458,9 @@ class GeneralParametersForm(param_forms.AdminParametersForm): enable_inactive_accounts = YesNoField( label=_("Enable inactive account tracking"), initial=True, - help_text=_("Allow the administrator to set a threshold beyond which " - "an account is considered inactive if the user hasn't logged.") + help_text=_("Allow the administrator to set a threshold (in days) " + "beyond which an account is considered inactive " + "if the user hasn't logged") ) inactive_account_threshold = forms.IntegerField( @@ -1081,8 +1082,9 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict([ ("enable_inactive_accounts", { "label": ugettext_lazy("Enable inactive account tracking"), "help_text": ugettext_lazy( - "Allow the administrator to set a threshold beyond which " - "an account is considered inactive if the user hasn't logged." + "Allow the administrator to set a threshold (in days) " + "beyond which an account is considered inactive " + "if the user hasn't logged" ), }), ("inactive_account_threshold", { From b2048598e638b9502e59f485bab7f4906ed09ba1 Mon Sep 17 00:00:00 2001 From: Spitap Date: Fri, 3 Mar 2023 09:50:40 +0100 Subject: [PATCH 6/7] Making codecov happy --- modoboa/dmarc/management/commands/import_aggregated_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa/dmarc/management/commands/import_aggregated_report.py b/modoboa/dmarc/management/commands/import_aggregated_report.py index ec1d2b582..28d211ee9 100644 --- a/modoboa/dmarc/management/commands/import_aggregated_report.py +++ b/modoboa/dmarc/management/commands/import_aggregated_report.py @@ -37,4 +37,4 @@ class Command(BaseCommand): elif options.get("imap"): lib.import_from_imap(options) else: - print("Nothing to do.", file=self.stdout) + print("Nothing to do.") From e8e26e1a6bd3e3f289fce6c99e51520a2f1cabc8 Mon Sep 17 00:00:00 2001 From: Spitfireap <45575529+Spitfireap@users.noreply.github.com> Date: Fri, 3 Mar 2023 09:00:06 +0000 Subject: [PATCH 7/7] Update app_settings.py --- modoboa/core/app_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index 811c2df62..46e83ee7d 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -460,7 +460,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm): initial=True, help_text=_("Allow the administrator to set a threshold (in days) " "beyond which an account is considered inactive " - "if the user hasn't logged") + "if the user hasn't logged in") ) inactive_account_threshold = forms.IntegerField( @@ -1084,7 +1084,7 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict([ "help_text": ugettext_lazy( "Allow the administrator to set a threshold (in days) " "beyond which an account is considered inactive " - "if the user hasn't logged" + "if the user hasn't logged in" ), }), ("inactive_account_threshold", {