mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
@@ -271,6 +271,16 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
|
||||
|
||||
sep3 = SeparatorField(label=ugettext_lazy("Miscellaneous"))
|
||||
|
||||
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"
|
||||
),
|
||||
widget=forms.TextInput(attrs={"class": "form-control"})
|
||||
)
|
||||
|
||||
top_notifications_check_interval = forms.IntegerField(
|
||||
label=_("Top notifications check interval"),
|
||||
initial=30,
|
||||
|
||||
@@ -92,14 +92,17 @@ def create_local_config(sender, **kwargs):
|
||||
@receiver(signals.pre_delete, sender=models.User)
|
||||
def update_permissions(sender, instance, **kwargs):
|
||||
"""Permissions cleanup."""
|
||||
from_user = get_request().user
|
||||
if from_user == instance:
|
||||
raise exceptions.PermDeniedException(
|
||||
_("You can't delete your own account")
|
||||
)
|
||||
request = get_request()
|
||||
# request migth be None (management command context)
|
||||
if request:
|
||||
from_user = request.user
|
||||
if from_user == instance:
|
||||
raise exceptions.PermDeniedException(
|
||||
_("You can't delete your own account")
|
||||
)
|
||||
|
||||
if not from_user.can_access(instance):
|
||||
raise exceptions.PermDeniedException
|
||||
if not from_user.can_access(instance):
|
||||
raise exceptions.PermDeniedException
|
||||
|
||||
# We send an additional signal before permissions are removed
|
||||
core_signals.account_deleted.send(
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Inactive accounts cleanup tool."""
|
||||
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from six.moves import input
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
|
||||
from modoboa.parameters import tools as param_tools
|
||||
|
||||
from ... import models
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Management command to clean inactive accounts."""
|
||||
|
||||
help = "Inactive accounts cleanup"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""Add extra arguments to command line."""
|
||||
parser.add_argument(
|
||||
"--delete", action="store_true", default=False,
|
||||
help="Delete inactive accounts (default is to disable)")
|
||||
parser.add_argument(
|
||||
"--dry-run", action="store_true", default=False,
|
||||
help="Only look for inactive accounts, no action")
|
||||
parser.add_argument(
|
||||
"--silent", action="store_true", default=False,
|
||||
help="Enable silent mode (no question)")
|
||||
parser.add_argument(
|
||||
"--verbose", action="store_true", default=False,
|
||||
help="Enable verbose mode")
|
||||
|
||||
def _log_inactive_accounts(self, qset):
|
||||
"""Log inactive accounts found."""
|
||||
print(
|
||||
"The following inactive accounts have been found:",
|
||||
file=self.stdout)
|
||||
for account in qset.values("username", "last_login"):
|
||||
print(
|
||||
"> {}\n (last login: {})".format(
|
||||
account["username"], account["last_login"]),
|
||||
file=self.stdout
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""Command entry point."""
|
||||
inactive_account_threshold = param_tools.get_global_parameter(
|
||||
"inactive_account_threshold")
|
||||
qset = models.User.objects.filter(
|
||||
is_active=True, is_superuser=False,
|
||||
last_login__lt=timezone.now() -
|
||||
relativedelta(days=inactive_account_threshold))
|
||||
if not qset.exists():
|
||||
return
|
||||
if options["verbose"]:
|
||||
self._log_inactive_accounts(qset)
|
||||
if options["dry_run"]:
|
||||
return
|
||||
action = "delete" if options["delete"] else "disable"
|
||||
if not options["silent"]:
|
||||
answer = input(
|
||||
"Do you want to {} those accounts? (y/N) ".format(action))
|
||||
if not answer.lower().startswith("y"):
|
||||
return
|
||||
if action == "disable":
|
||||
qset.update(is_active=False)
|
||||
else:
|
||||
qset.delete()
|
||||
@@ -5,10 +5,12 @@ from __future__ import unicode_literals
|
||||
import httmock
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from six import StringIO
|
||||
|
||||
from django.core import management
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
|
||||
@@ -72,6 +74,39 @@ class ManagementCommandsTestCase(TestCase):
|
||||
management.call_command("cleanlogs")
|
||||
self.assertEqual(models.Log.objects.count(), 1)
|
||||
|
||||
def test_clean_inactive_accounts(self):
|
||||
"""Run clean_inactive_accounts command."""
|
||||
management.call_command("load_initial_data")
|
||||
|
||||
# no inactive account, should exit normaly
|
||||
management.call_command("clean_inactive_accounts")
|
||||
|
||||
last_login = timezone.now() - relativedelta(days=45)
|
||||
account = factories.UserFactory(
|
||||
username="user1@domain.test", groups=("SimpleUsers", ),
|
||||
last_login=last_login
|
||||
)
|
||||
management.call_command("clean_inactive_accounts", "--dry-run")
|
||||
account.refresh_from_db()
|
||||
self.assertTrue(account.is_active)
|
||||
|
||||
out = StringIO()
|
||||
management.call_command(
|
||||
"clean_inactive_accounts", "--verbose", "--dry-run", stdout=out)
|
||||
self.assertIn("user1@domain.test", out.getvalue())
|
||||
|
||||
management.call_command("clean_inactive_accounts", "--silent")
|
||||
account.refresh_from_db()
|
||||
self.assertFalse(account.is_active)
|
||||
|
||||
account.is_active = True
|
||||
account.save(update_fields=["is_active"])
|
||||
|
||||
management.call_command(
|
||||
"clean_inactive_accounts", "--silent", "--delete")
|
||||
with self.assertRaises(models.User.DoesNotExist):
|
||||
account.refresh_from_db()
|
||||
|
||||
|
||||
class ProfileTestCase(ModoTestCase):
|
||||
"""Profile related tests."""
|
||||
|
||||
@@ -118,9 +118,9 @@ def ungrant_access_to_object(obj, user=None):
|
||||
|
||||
|
||||
def ungrant_access_to_objects(objects):
|
||||
"""Cancel all accesses for a given objects list
|
||||
"""Cancel all accesses for a given object list.
|
||||
|
||||
:param objects: a list of objects inheriting from ``model.Model``
|
||||
:param objects: a list of objects inheriting from ``models.Model``
|
||||
"""
|
||||
for obj in objects:
|
||||
ct = ContentType.objects.get_for_model(obj)
|
||||
|
||||
Reference in New Issue
Block a user