0001-misc-add-a-DeletedUser-model-to-keep-metadata-about-.patch
src/authentic2/app_settings.py | ||
---|---|---|
314 | 314 |
A2_ACCEPT_EMAIL_AUTHENTICATION=Setting( |
315 | 315 |
default=True, |
316 | 316 |
definition='Enable authentication by email'), |
317 |
A2_USER_DELETED_KEEP_DATA=Setting( |
|
318 |
default=['email', 'uuid'], |
|
319 |
definition='User data to keep after deletion'), |
|
320 |
A2_USER_DELETED_KEEP_DATA_DAYS=Setting( |
|
321 |
default=365, |
|
322 |
definition='Number of days to keep data on deleted users'), |
|
317 | 323 |
) |
318 | 324 | |
319 | 325 |
app_settings = AppSettings(default_settings) |
src/authentic2/custom_user/managers.py | ||
---|---|---|
22 | 22 |
from django.utils import timezone |
23 | 23 |
from django.contrib.auth.models import BaseUserManager |
24 | 24 | |
25 |
from authentic2 import app_settings |
|
25 | 26 |
from authentic2.models import Attribute |
26 | 27 | |
27 | 28 | |
... | ... | |
88 | 89 |
@transaction.atomic |
89 | 90 |
def cleanup(self, threshold=600, timestamp=None): |
90 | 91 |
'''Delete all deleted users for more than 10 minutes.''' |
92 |
from .models import DeletedUser |
|
93 | ||
91 | 94 |
not_after = (timestamp or timezone.now()) - datetime.timedelta(seconds=threshold) |
92 | 95 |
qs = self.filter(deleted__lt=not_after) |
93 | 96 | |
... | ... | |
98 | 101 |
for user in loaded: |
99 | 102 |
logger.info(u'deleted account %s', user) |
100 | 103 |
transaction.on_commit(log) |
104 |
deleted_users = [] |
|
105 |
for user in qs: |
|
106 |
deleted_user = DeletedUser(deleted=user.deleted, old_user_id=user.id) |
|
107 |
if 'email' in app_settings.A2_USER_DELETED_KEEP_DATA: |
|
108 |
deleted_user.old_email = user.email |
|
109 |
if 'uuid' in app_settings.A2_USER_DELETED_KEEP_DATA: |
|
110 |
deleted_user.old_uuid = user.uuid |
|
111 |
deleted_users.append(deleted_user) |
|
112 |
DeletedUser.objects.bulk_create(deleted_users) |
|
101 | 113 |
qs.delete() |
src/authentic2/custom_user/models.py | ||
---|---|---|
1 |
# coding: utf-8 |
|
1 | 2 |
# authentic2 - versatile identity manager |
2 | 3 |
# Copyright (C) 2010-2019 Entr'ouvert |
3 | 4 |
# |
... | ... | |
14 | 15 |
# You should have received a copy of the GNU Affero General Public License |
15 | 16 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 17 | |
18 |
from __future__ import unicode_literals |
|
19 | ||
20 |
import datetime |
|
17 | 21 |
import random |
18 | 22 | |
19 | 23 |
from django.db import models, transaction |
... | ... | |
27 | 31 |
except ImportError: |
28 | 32 |
from django.contrib.contenttypes.generic import GenericRelation |
29 | 33 |
from django.contrib.auth.models import AbstractBaseUser |
34 |
from django.contrib.postgres.fields import JSONField |
|
30 | 35 | |
31 | 36 |
from django_rbac.models import PermissionMixin |
32 | 37 |
from django_rbac.utils import get_role_parenting_model |
... | ... | |
234 | 239 |
def __str__(self): |
235 | 240 |
human_name = self.username or self.email or self.get_full_name() |
236 | 241 |
short_id = self.uuid[:6] |
237 |
return u'%s (%s)' % (human_name, short_id)
|
|
242 |
return '%s (%s)' % (human_name, short_id) |
|
238 | 243 | |
239 | 244 |
def __repr__(self): |
240 | 245 |
return '<User: %r>' % six.text_type(self) |
... | ... | |
341 | 346 |
self.deleted = timestamp or timezone.now() |
342 | 347 |
if save: |
343 | 348 |
self.save(update_fields=['email', 'email_verified', 'is_active', 'deleted']) |
349 | ||
350 | ||
351 |
@six.python_2_unicode_compatible |
|
352 |
class DeletedUser(models.Model): |
|
353 |
deleted = models.DateTimeField( |
|
354 |
verbose_name=_('Deletion date')) |
|
355 |
old_uuid = models.TextField( |
|
356 |
verbose_name=_('Old UUID'), |
|
357 |
null=True, |
|
358 |
blank=True) |
|
359 |
old_user_id = models.PositiveIntegerField( |
|
360 |
verbose_name=_('Old user id'), |
|
361 |
null=True, |
|
362 |
blank=True) |
|
363 |
old_email = models.EmailField( |
|
364 |
verbose_name=_('Old email adress'), |
|
365 |
null=True, |
|
366 |
blank=True) |
|
367 |
old_data = JSONField( |
|
368 |
verbose_name=_('Old data'), |
|
369 |
null=True, |
|
370 |
blank=True) |
|
371 | ||
372 |
@classmethod |
|
373 |
def cleanup(cls, threshold=None, timestamp=None): |
|
374 |
threshold = threshold or (timezone.now() - datetime.timedelta(days=app_settings.A2_USER_DELETED_KEEP_DATA_DAYS)) |
|
375 |
cls.objects.filter(deleted__lt=threshold).delete() |
|
376 | ||
377 |
def __str__(self): |
|
378 |
return 'DeletedUser(old_id=%s, old_uuid=%s…, old_email=%s)' % ( |
|
379 |
self.old_user_id or '-', |
|
380 |
(self.old_uuid or '')[:6], |
|
381 |
self.old_email or '-') |
|
382 | ||
383 |
class Meta: |
|
384 |
verbose_name = _('deleted user') |
|
385 |
verbose_name_plural = _('deleted users') |
|
386 |
ordering = ('deleted', 'id') |
tests/test_cleanup.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
import datetime |
18 | 18 | |
19 |
from django.contrib.auth import get_user_model |
|
20 | 19 |
from django.utils.timezone import now |
21 | 20 | |
21 |
from authentic2.custom_user.models import User, DeletedUser |
|
22 | 22 | |
23 |
def test_deleted_user_cleanup(db): |
|
24 |
User = get_user_model() |
|
25 |
u = User.objects.create(username='john.doe') |
|
23 | ||
24 |
def test_deleted_user_cleanup(db, freezer): |
|
25 |
freezer.move_to('2020-01-01') |
|
26 |
u = User.objects.create(username='john.doe', email='john@example.com') |
|
26 | 27 |
assert User.objects.count() == 1 |
28 |
assert DeletedUser.objects.count() == 0 |
|
27 | 29 |
u.mark_as_deleted() |
28 | 30 |
User.objects.cleanup(timestamp=now() + datetime.timedelta(seconds=700)) |
29 | 31 |
assert User.objects.count() == 0 |
32 |
assert DeletedUser.objects.count() == 1 |
|
33 |
deleted_user = DeletedUser.objects.get(old_user_id=u.id) |
|
34 |
assert deleted_user.deleted == u.deleted |
|
35 |
assert deleted_user.old_email == u.email |
|
36 |
assert deleted_user.old_uuid == u.uuid |
|
37 |
assert deleted_user.old_data is None |
|
38 |
freezer.move_to(datetime.timedelta(days=365)) |
|
39 |
DeletedUser.cleanup() |
|
40 |
assert DeletedUser.objects.count() == 1, 'DeletedUser are deleted after 365 days' |
|
41 |
freezer.move_to(datetime.timedelta(seconds=1)) |
|
42 |
DeletedUser.cleanup() |
|
43 |
assert DeletedUser.objects.count() == 0, 'DeletedUser are deleted after 365 days' |
|
44 | ||
30 |
- |