0001-api-add-timestamp-parameter-to-users-synchronization.patch
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 |
- |