141 lines
5.7 KiB
Python
141 lines
5.7 KiB
Python
from __future__ import print_function
|
|
|
|
import logging
|
|
import datetime
|
|
|
|
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 authentic2.models import DeletedUser
|
|
|
|
from django.conf import settings
|
|
|
|
def print_table(table):
|
|
col_width = [max(len(x) for x in col) for col in zip(*table)]
|
|
for line in table:
|
|
line = u"| " + u" | ".join(u"{0:>{1}}".format(x, col_width[i])
|
|
for i, x in enumerate(line)) + u" |"
|
|
print(line)
|
|
|
|
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, '
|
|
'the Django filter key and value are separated by character =', action='append',
|
|
default=[]
|
|
)
|
|
parser.add_argument(
|
|
'--from-email', default=settings.DEFAULT_FROM_EMAIL,
|
|
help='sender address for notifications, default is DEFAULT_FROM_EMAIL from settings'
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
log = logging.getLogger(__name__)
|
|
try:
|
|
self.clean_unused_acccounts(*args, **options)
|
|
except:
|
|
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':
|
|
logging.basicConfig(level=logging.WARNING)
|
|
elif options['verbosity'] == '2':
|
|
logging.basicConfig(level=logging.INFO)
|
|
elif options['verbosity'] == '3':
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
|
|
log = logging.getLogger(__name__)
|
|
n = now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
self.fake = options['fake']
|
|
self.from_email = options['from_email']
|
|
if self.fake:
|
|
log.info('fake call to clean-unused-accounts')
|
|
users = get_user_model().objects.all()
|
|
if options['filter']:
|
|
for f in options['filter']:
|
|
key, value = f.split('=', 1)
|
|
try:
|
|
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)
|
|
|
|
|
|
def send_mail(self, prefix, user, ctx):
|
|
log = logging.getLogger(__name__)
|
|
|
|
if not user.email:
|
|
log.debug('%s has no email, no mail sent', user)
|
|
subject = render_to_string(prefix + '_subject.txt', ctx).strip()
|
|
body = render_to_string(prefix + '_body.txt', ctx)
|
|
if not self.fake:
|
|
try:
|
|
log.debug('sending mail to %s', user.email)
|
|
send_mail(subject, body, self.from_email, [user.email])
|
|
except:
|
|
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)
|
|
if not self.fake:
|
|
DeletedUser.objects.delete_user(user)
|