From b8231c9d21bd4ffe10e413693c04e14ef7da8768 Mon Sep 17 00:00:00 2001
From: Paul Marillonnet <pmarillonnet@entrouvert.com>
Date: Tue, 26 Mar 2019 17:29:43 +0100
Subject: [PATCH] WIP clean unused accounts depending on OUs (#26909)

---
 src/authentic2/a2_rbac/models.py              |  9 ++
 .../commands/clean-unused-accounts.py         | 94 ++++++++-----------
 2 files changed, 49 insertions(+), 54 deletions(-)

diff --git a/src/authentic2/a2_rbac/models.py b/src/authentic2/a2_rbac/models.py
index 27388f05..a2ae655d 100644
--- a/src/authentic2/a2_rbac/models.py
+++ b/src/authentic2/a2_rbac/models.py
@@ -76,6 +76,15 @@ class OrganizationalUnit(OrganizationalUnitAbstractBase):
         choices=USER_ADD_PASSWD_POLICY_CHOICES,
         default=0)
 
+    clean_unused_accounts_reminder = models.IntegerField(
+        verbose_name=_('Days after which the user receives an account deletion '
+            'warnin.'),
+        default=0)
+
+    clean_unused_accounts_deletion = models.IntegerField(
+        verbose_name=_('Delay in days before cleaning unused accounts'),
+        default=0)
+
     objects = managers.OrganizationalUnitManager()
 
     class Meta:
diff --git a/src/authentic2/management/commands/clean-unused-accounts.py b/src/authentic2/management/commands/clean-unused-accounts.py
index 0340b15a..7f61e65c 100644
--- a/src/authentic2/management/commands/clean-unused-accounts.py
+++ b/src/authentic2/management/commands/clean-unused-accounts.py
@@ -1,13 +1,14 @@
 from __future__ import print_function
 
 import logging
-import datetime
 
+from datetime import timedelta
 from django.contrib.auth import get_user_model
 from django.core.management.base import BaseCommand, CommandError
 from django.core.mail import send_mail
 from django.utils.timezone import now
 from django.template.loader import render_to_string
+from django_rbac.utils import get_ou_model
 
 from authentic2.models import DeletedUser
 
@@ -24,18 +25,6 @@ class Command(BaseCommand):
     help = '''Clean unused accounts'''
 
     def add_arguments(self, parser):
-        parser.add_argument('clean_threshold', type=int)
-        parser.add_argument(
-            "--alert-thresholds",
-            help='list of durations before sending an alert '
-                 'message for unused account, default is none',
-            default=None)
-        parser.add_argument(
-            "--period", type=int,
-            help='period between two calls to '
-                 'clean-unused-accounts as days, default is 1',
-            default=1
-        )
         parser.add_argument("--fake", action='store_true', help='do nothing', default=False)
         parser.add_argument(
             "--filter", help='filter to apply to the user queryset, '
@@ -55,13 +44,6 @@ class Command(BaseCommand):
             log.exception('failure while cleaning unused accounts')
 
     def clean_unused_acccounts(self, *args, **options):
-        if options['period'] < 1:
-            raise CommandError('period must be > 0')
-
-        clean_threshold = options['clean_threshold']
-        if clean_threshold < 1:
-            raise CommandError('clean_threshold must be an integer > 0')
-
         if options['verbosity'] == '0':
             logging.basicConfig(level=logging.CRITICAL)
         if options['verbosity'] == '1':
@@ -85,36 +67,35 @@ class Command(BaseCommand):
                     users = users.filter(**{key: value})
                 except:
                     raise CommandError('invalid --filter %s' % f)
-        if options['alert_thresholds']:
-            alert_thresholds = options['alert_thresholds']
-            alert_thresholds = alert_thresholds.split(',')
-            try:
-                alert_thresholds = map(int, alert_thresholds)
-            except ValueError:
-                raise CommandError('alert_thresholds must be a comma '
-                        'separated list of integers')
-            for threshold in alert_thresholds:
-                if not (0 < threshold < clean_threshold):
-                    raise CommandError('alert-threshold must a positive integer '
-                            'inferior to clean-threshold: 0 < %d < %d' % (
-                                threshold, clean_threshold))
-            for threshold in alert_thresholds:
-                a = n - datetime.timedelta(days=threshold)
-                b = n - datetime.timedelta(days=threshold-options['period'])
-                for user in users.filter(last_login__lt=b, last_login__gte=a):
-                    log.info('%s last login %d days ago, sending alert', user, threshold)
-                    self.send_alert(user, threshold, clean_threshold-threshold)
-        threshold = n - datetime.timedelta(days=clean_threshold)
-        for user in users.filter(last_login__lt=threshold):
-            d = n - user.last_login
-            log.info('%s last login %d days ago, deleting user', user, d.days)
-            self.delete_user(user, clean_threshold)
-
-
-    def send_alert(self, user, threshold, clean_threshold):
-        ctx = { 'user': user, 'threshold': threshold,
-                'clean_threshold': clean_threshold }
-        self.send_mail('authentic2/unused_account_alert', user, ctx)
+        for ou in get_ou_model().objects.all():
+            if not ou.clean_unused_accounts_deletion:
+                continue
+
+            deletion = timedelta(days=ou.clean_unused_accounts_deletion)
+            reminder = timedelta(days=ou.clean_unused_accounts_reminder)
+
+            for user in users.filter(ou=ou, last_login_lt=n-reminder):
+                if n - user.last_login >= deletion:
+                    log.info(
+                        '%s last login more than %d days ago, deleting user',
+                        user, ou.clean_unused_accounts_deletion)
+                    self.delete_user(user, clean_threshold)
+                elif n - user.last_login - reminder <= timedelta(days=1):
+                    log.info('%s last login %d days ago, sending alert', user,
+                        ou.clean_unused_accounts_reminder)
+                    self.send_alert(user)
+
+
+    def send_alert(self, user):
+        ctx = {
+            'user': user,
+            'threshold': user.ou.clean_unused_accounts_reminder,
+            'clean_threshold': user.ou.clean_unused_accounts_deletion
+        }
+        self.send_mail(
+                'authentic2/unused_account_alert_{}'.format(user.ou.slug),
+                'authentic2/unused_account_alert', #default fallback template
+                user, ctx)
 
 
     def send_mail(self, prefix, user, ctx):
@@ -132,9 +113,14 @@ class Command(BaseCommand):
                 log.exception('email sending failure')
 
 
-    def delete_user(self, user, threshold):
-        ctx = { 'user': user, 'threshold': threshold }
-        self.send_mail('authentic2/unused_account_delete', user,
-                ctx)
+    def delete_user(self, user):
+        ctx = {
+            'user': user,
+            'threshold': user.ou.clean_unused_accounts_deletion
+        }
+        self.send_mail(
+                'authentic2/unused_account_delete_{}'.format(user.ou.slug),
+                'authentic2/unused_account_delete', #default fallback template
+                user, ctx)
         if not self.fake:
             DeletedUser.objects.delete_user(user)
-- 
2.20.1

