Projet

Général

Profil

0001-misc-add-a-DeletedUser-model-to-keep-metadata-about-.patch

Benjamin Dauvergne, 21 avril 2020 23:26

Télécharger (6,79 ko)

Voir les différences:

Subject: [PATCH 1/2] misc: add a DeletedUser model to keep metadata about
 deleted users (#41933)

 src/authentic2/app_settings.py         |  6 ++++
 src/authentic2/custom_user/managers.py | 12 +++++++
 src/authentic2/custom_user/models.py   | 45 +++++++++++++++++++++++++-
 tests/test_cleanup.py                  | 23 ++++++++++---
 4 files changed, 81 insertions(+), 5 deletions(-)
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
-