Projet

Général

Profil

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

Paul Marillonnet, 10 février 2022 17:59

Télécharger (7,43 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
......
57 58
from . import api_mixins, app_settings, decorators, hooks
58 59
from .a2_rbac.models import OrganizationalUnit, Role
59 60
from .a2_rbac.utils import get_default_ou
60
from .custom_user.models import User
61
from .apps.journal.models import Event
62
from .custom_user.models import DeletedUser, User
61 63
from .journal_event_types import UserLogin, UserRegistration
62 64
from .models import Attribute, PasswordReset, Service
63 65
from .passwords import get_password_checker
......
795 797
    class SynchronizationSerializer(serializers.Serializer):
796 798
        known_uuids = serializers.ListField(child=serializers.CharField())
797 799
        full_known_users = serializers.BooleanField(required=False)
800
        timestamp = serializers.DateTimeField(required=False)
798 801

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

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

  
1185 1185

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  
1270

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