Projet

Général

Profil

0002-misc-do-not-update-last_account_deletion_alert-on-lo.patch

Benjamin Dauvergne, 03 avril 2020 10:39

Télécharger (7,26 ko)

Voir les différences:

Subject: [PATCH 2/2] misc: do not update last_account_deletion_alert on login
 (#41284)

 .../commands/clean-unused-accounts.py         | 84 ++++++++++---------
 src/authentic2/utils/__init__.py              |  2 -
 2 files changed, 46 insertions(+), 40 deletions(-)
src/authentic2/management/commands/clean-unused-accounts.py
22 22
from datetime import timedelta
23 23
from django.contrib.auth import get_user_model
24 24
from django.core.management.base import BaseCommand
25
from django.db.transaction import atomic
26
from django.db.models import F
25 27
from django.utils import timezone
26 28
from django.utils.six.moves.urllib import parse as urlparse
27 29
from django_rbac.utils import get_ou_model
......
36 38
User = get_user_model()
37 39

  
38 40

  
39
def print_table(table):
40
    col_width = [max(len(x) for x in col) for col in zip(*table)]
41
    for line in table:
42
        line = u"| " + u" | ".join(u"{0:>{1}}".format(x, col_width[i]) for i, x in enumerate(line)) + u" |"
43
        print(line)
44

  
45

  
46 41
class Command(BaseCommand):
47 42
    help = '''Clean unused accounts'''
48 43

  
44
    verbosity_to_log_level = {
45
        0: logging.CRITICAL,
46
        1: logging.WARNING,
47
        2: logging.INFO,
48
        3: logging.DEBUG,
49
    }
50

  
49 51
    def add_arguments(self, parser):
50 52
        parser.add_argument("--fake", action='store_true', help='do nothing', default=False)
51 53

  
52 54
    def handle(self, *args, **options):
53
        if options['verbosity'] == '0':
54
            logger.setLevel(level=logging.CRITICAL)
55
        if options['verbosity'] == '1':
56
            logger.setLevel(level=logging.WARNING)
57
        elif options['verbosity'] == '2':
58
            logger.setLevel(level=logging.INFO)
59
        elif options['verbosity'] == '3':
60
            logger.setLevel(level=logging.DEBUG)
61 55
        self.fake = options['fake']
62 56

  
63
        self.clean_unused_accounts()
57
        # add StreamHandler for console output
58
        handler = logging.StreamHandler()
59
        handler.setLevel(level=self.verbosity_to_log_level[options['verbosity']])
60
        logger.addHandler(handler)
64 61

  
65
    def clean_unused_accounts(self):
66
        now = timezone.now()
62
        # prevent logging to external logs when fake
67 63
        if self.fake:
68
            logger.info('fake call to clean-unused-accounts')
64
            logger.propagate = False
69 65

  
66
        self.now = timezone.now()
67
        self.clean_unused_accounts()
68

  
69
    def clean_unused_accounts(self):
70 70
        for ou in get_ou_model().objects.filter(clean_unused_accounts_alert__isnull=False):
71 71
            alert_delay = timedelta(days=ou.clean_unused_accounts_alert)
72 72
            deletion_delay = timedelta(days=ou.clean_unused_accounts_deletion)
73
            users = User.objects.filter(ou=ou, last_login__lte=now - alert_delay)
73
            ou_users = User.objects.filter(ou=ou)
74

  
75
            # reset last_account_deletion_alert for users which connected since last alert
76
            active_users = ou_users.filter(last_login__gte=F('last_account_deletion_alert'))
77
            active_users.update(last_account_deletion_alert=None)
74 78

  
75
            for user in users.filter(last_account_deletion_alert__isnull=True):
79
            inactive_users = ou_users.filter(last_login__lte=self.now - alert_delay)
80

  
81
            # send first alert
82
            inactive_users_first_alert = inactive_users.filter(last_account_deletion_alert__isnull=True)
83
            days_to_deletion = ou.clean_unused_accounts_deletion - ou.clean_unused_accounts_alert
84
            for user in inactive_users_first_alert:
76 85
                logger.info('%s last login %d days ago, sending alert', user, ou.clean_unused_accounts_alert)
77
                self.send_alert(user)
86
                self.send_alert(user, days_to_deletion)
78 87

  
79
            to_delete = users.filter(
80
                last_login__lte=now - deletion_delay,
88
            inactive_users_to_delete = inactive_users.filter(
89
                last_login__lte=self.now - deletion_delay,
81 90
                # ensure respect of alert delay before deletion
82
                last_account_deletion_alert__lte=now - (deletion_delay - alert_delay)
91
                last_account_deletion_alert__lte=self.now - (deletion_delay - alert_delay)
83 92
            )
84
            for user in to_delete:
93
            for user in inactive_users_to_delete:
85 94
                logger.info(
86 95
                    '%s last login more than %d days ago, deleting user', user,
87 96
                    ou.clean_unused_accounts_deletion)
88 97
                self.delete_user(user)
89 98

  
90
    def send_alert(self, user):
91
        days_to_deletion = user.ou.clean_unused_accounts_deletion - user.ou.clean_unused_accounts_alert
99
    def send_alert(self, user, days_to_deletion):
92 100
        ctx = {
93 101
            'user': user,
94 102
            'days_to_deletion': days_to_deletion,
95 103
            'login_url': urlparse.urljoin(settings.SITE_BASE_URL, settings.LOGIN_URL),
96 104
        }
97 105
        try:
98
            self.send_mail('authentic2/unused_account_alert', user, ctx)
106
            with atomic():
107
                if not self.fake:
108
                    User.objects.filter(pk=user.pk).update(last_account_deletion_alert=self.now)
109
                self.send_mail('authentic2/unused_account_alert', user, ctx)
99 110
        except smtplib.SMTPException as e:
100
            logger.exception('email sending failure: %s', e)
101
        else:
102
            if not self.fake:
103
                user.last_account_deletion_alert = timezone.now()
104
                user.save()
111
            logger.warning('email sending failure: %s', e)
105 112

  
106 113
    def send_mail(self, prefix, user, ctx):
107 114
        if not user.email:
......
113 120
    def delete_user(self, user):
114 121
        ctx = {'user': user}
115 122
        try:
116
            self.send_mail('authentic2/unused_account_delete', user, ctx)
123
            with atomic():
124
                if not self.fake:
125
                    DeletedUser.objects.delete_user(user)
126
                self.send_mail('authentic2/unused_account_delete', user, ctx)
117 127
        except smtplib.SMTPException as e:
118
            logger.exception('email sending failure: %s', e)
119
        if not self.fake:
120
            DeletedUser.objects.delete_user(user)
128
            logger.warning('email sending failure: %s', e)
src/authentic2/utils/__init__.py
437 437
    if constants.LAST_LOGIN_SESSION_KEY not in request.session:
438 438
        request.session[constants.LAST_LOGIN_SESSION_KEY] = \
439 439
            localize(to_current_timezone(last_login), True)
440
    user.last_account_deletion_alert = None
441
    user.save()
442 440
    record_authentication_event(request, how, nonce=nonce)
443 441
    hooks.call_hooks('event', name='login', user=user, how=how, service=service_slug)
444 442
    return continue_to_next_url(request, **kwargs)
445
-