Projet

Général

Profil

0001-api-add-timestamp-parameter-to-users-synchronization.patch

Paul Marillonnet, 04 novembre 2021 15:10

Télécharger (7,4 ko)

Voir les différences:

Subject: [PATCH] api: add timestamp parameter to users synchronization api
 (#57564)

 src/authentic2/api_views.py | 35 ++++++++++++---
 tests/test_api.py           | 85 +++++++++++++++++++++++++++++++++++++
 2 files changed, 115 insertions(+), 5 deletions(-)
src/authentic2/api_views.py
23 23
from django.conf import settings
24 24
from django.contrib.auth import get_user_model
25 25
from django.contrib.auth.hashers import identify_hasher
26
from django.contrib.contenttypes.models import ContentType
26 27
from django.core.exceptions import MultipleObjectsReturned
27 28
from django.db import models
28 29
from django.shortcuts import get_object_or_404
......
58 59

  
59 60
from . import api_mixins, app_settings, decorators, hooks
60 61
from .a2_rbac.utils import get_default_ou
61
from .custom_user.models import User
62
from .apps.journal.models import Event
63
from .custom_user.models import DeletedUser, User
62 64
from .journal_event_types import UserLogin, UserRegistration
63 65
from .models import Attribute, PasswordReset, Service
64 66
from .passwords import get_password_checker
......
796 798
    class SynchronizationSerializer(serializers.Serializer):
797 799
        known_uuids = serializers.ListField(child=serializers.CharField())
798 800
        full_known_users = serializers.BooleanField(required=False)
801
        timestamp = serializers.DateTimeField(required=False)
799 802

  
800
    def check_uuids(self, uuids):
803
    def check_uuids(self, uuids, timestamp=None):
801 804
        User = get_user_model()
802 805
        known_uuids = User.objects.filter(uuid__in=uuids).values_list('uuid', flat=True)
803
        return (known_uuids, set(uuids) - set(known_uuids))
806
        unknown_uuids = set(uuids) - set(known_uuids)
807
        modified_users_uuids = []
808
        if timestamp:
809
            unknown_uuids = DeletedUser.objects.filter(
810
                old_uuid__in=unknown_uuids, deleted__gt=timestamp
811
            ).values_list('old_uuid', flat=True)
812
            user_ct = ContentType.objects.get_for_model(get_user_model())
813
            user_events = Event.objects.filter(
814
                reference_ct_ids__contains=[user_ct.id], timestamp__gt=timestamp
815
            )
816
            for user_event in user_events:
817
                for reference in user_event.references:
818
                    if (
819
                        reference is not None  # xxx None references in journal?!
820
                        and ContentType.objects.get_for_model(reference) == user_ct
821
                        and reference.uuid not in modified_users_uuids
822
                        and reference.uuid not in unknown_uuids
823
                    ):
824
                        modified_users_uuids.append(reference.uuid)
825
        return (known_uuids, unknown_uuids, modified_users_uuids)
804 826

  
805 827
    def get_users_from_uuids(self, known_uuids):
806 828
        User = get_user_model()
......
814 836
            response = {'result': 0, 'errors': serializer.errors}
815 837
            return Response(response, status.HTTP_400_BAD_REQUEST)
816 838
        hooks.call_hooks('api_modify_serializer_after_validation', self, serializer)
817
        known_uuids, unknown_uuids = self.check_uuids(serializer.validated_data.get('known_uuids', []))
818
        full_known_users = serializer.validated_data.get('full_known_users', None)
839
        known_uuids = serializer.validated_data.get('known_uuids', [])
840
        timestamp = serializer.validated_data.get('timestamp', None)
841
        known_uuids, unknown_uuids, modified_users_uuids = self.check_uuids(known_uuids, timestamp)
819 842
        data = {
820 843
            'result': 1,
821 844
            'unknown_uuids': unknown_uuids,
845
            'modified_users_uuids': modified_users_uuids,
822 846
        }
847
        full_known_users = serializer.validated_data.get('full_known_users', None)
823 848
        if full_known_users:
824 849
            if len(known_uuids) > 1000:
825 850
                known_uuids = known_uuids[:1000]
tests/test_api.py
1184 1184
        }.issubset(set(user_dict.keys()))
1185 1185

  
1186 1186

  
1187
def test_user_synchronization_timestamp(app, admin):
1188
    headers = basic_authorization_header(admin)
1189
    url = reverse('a2-api-users-synchronization')
1190
    now = datetime.datetime.now()
1191

  
1192
    ou = get_default_ou()
1193
    User = get_user_model()
1194
    users = []
1195

  
1196
    for i in range(6):
1197
        users.append(
1198
            User.objects.create(
1199
                first_name='john%s' % i,
1200
                last_name='doe',
1201
                username='user%s' % i,
1202
                email='user%s' % i,
1203
                ou=ou,
1204
            )
1205
        )
1206

  
1207
    for i, event_name in enumerate(
1208
        [
1209
            'manager.user.creation',
1210
            'manager.user.profile.edit',
1211
            'manager.user.activation',
1212
            'manager.user.deactivation',
1213
            'manager.user.password.change.force',
1214
            'manager.user.password.change.unforce',
1215
        ]
1216
    ):
1217
        event_type = EventType.objects.get_for_name(event_name)
1218
        Event.objects.create(
1219
            type=event_type,
1220
            timestamp=now - datetime.timedelta(days=i, hours=1),
1221
            references=[users[i]],
1222
        )
1223

  
1224
    content = {
1225
        'known_uuids': [user.uuid for user in users],
1226
        'timestamp': (now - datetime.timedelta(days=3)).isoformat(),
1227
    }
1228

  
1229
    response = app.post(url, params=content, headers=headers)
1230

  
1231
    for user in users[:3]:
1232
        assert user.uuid in response.json['modified_users_uuids']
1233
        assert user.uuid not in response.json['unknown_uuids']
1234
    for user in users[3:]:
1235
        assert user.uuid not in response.json['modified_users_uuids']
1236
        assert user.uuid not in response.json['unknown_uuids']
1237

  
1238
    for user in users[:3]:
1239
        user.delete()
1240

  
1241
    content['timestamp'] = (now - datetime.timedelta(days=7)).isoformat()
1242

  
1243
    response = app.post(url, params=content, headers=headers)
1244

  
1245
    for user in users[:3]:
1246
        assert user.uuid not in response.json['modified_users_uuids']
1247
        assert user.uuid in response.json['unknown_uuids']
1248
    for user in users[3:]:
1249
        assert user.uuid in response.json['modified_users_uuids']
1250
        assert user.uuid not in response.json['unknown_uuids']
1251

  
1252
    for user in users[3:]:
1253
        user.delete()
1254

  
1255
    response = app.post(url, params=content, headers=headers)
1256

  
1257
    assert not response.json['modified_users_uuids']
1258
    for user in users:
1259
        assert user.uuid in response.json['unknown_uuids']
1260

  
1261
    for user in users[:3]:
1262
        content['known_uuids'].remove(user.uuid)
1263

  
1264
    response = app.post(url, params=content, headers=headers)
1265

  
1266
    assert not response.json['modified_users_uuids']
1267
    assert len(response.json['unknown_uuids']) == 3
1268
    for user in users[3:]:
1269
        assert user.uuid in response.json['unknown_uuids']
1270

  
1271

  
1187 1272
def test_api_drf_authentication_class(app, admin, user_ou1, oidc_client):
1188 1273
    url = '/api/users/%s/' % user_ou1.uuid
1189 1274
    # test invalid client
1190
-