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)
|