Projet

Général

Profil

0001-wip.patch

Valentin Deniaud, 18 mai 2021 17:57

Télécharger (23,3 ko)

Voir les différences:

Subject: [PATCH] wip

 src/authentic2/api_views.py                   |  31 +++
 src/authentic2/apps/journal/models.py         |   3 +
 src/authentic2/journal_event_types.py         | 219 ++++++++++++++++++
 src/authentic2/manager/journal_event_types.py |  17 +-
 src/authentic2/manager/journal_views.py       |   8 +
 tests/test_manager_journal.py                 | 133 +++++++++++
 6 files changed, 395 insertions(+), 16 deletions(-)
src/authentic2/api_views.py
343 343
    def rpc(self, request, serializer):
344 344
        serializer.user.set_password(serializer.validated_data['new_password'])
345 345
        serializer.user.save()
346
        request.journal.record('api.user.password.change', serializer=serializer)
346 347
        return {'result': 1}, status.HTTP_200_OK
347 348

  
348 349

  
......
778 779
            if not self.request.user.has_perm(perm):
779 780
                raise PermissionDenied(u'You do not have permission %s' % perm)
780 781

  
782
    def perform_create(self, serializer):
783
        super().perform_create(serializer)
784
        self.request.journal.record('api.user.creation', target_user=serializer.instance)
785

  
786
    def perform_update(self, serializer):
787
        super().perform_update(serializer)
788
        self.request.journal.record('api.user.profile.edit', serializer=serializer)
789

  
781 790
    def perform_destroy(self, instance):
782 791
        self.check_perm('custom_user.delete_user', instance.ou)
783 792
        super(UsersAPI, self).perform_destroy(instance)
793
        self.request.journal.record('api.user.deletion', target_user=instance)
784 794

  
785 795
    class SynchronizationSerializer(serializers.Serializer):
786 796
        known_uuids = serializers.ListField(child=serializers.CharField())
......
820 830
            )
821 831

  
822 832
        utils.send_password_reset_mail(user, request=request)
833
        request.journal.record('api.user.password.reset.request', target_user=user)
823 834
        return Response(status=status.HTTP_204_NO_CONTENT)
824 835

  
825 836
    @action(detail=True, methods=['post'], permission_classes=(DjangoPermission('custom_user.change_user'),))
......
876 887
        if not self.request.user.has_perm(perm='a2_rbac.delete_role', obj=instance):
877 888
            raise PermissionDenied(u'User %s can\'t create role %s' % (self.request.user, instance))
878 889
        super(RolesAPI, self).perform_destroy(instance)
890
        self.request.journal.record('api.role.deletion', role=instance)
891

  
892
    def perform_create(self, serializer):
893
        super().perform_create(serializer)
894
        self.request.journal.record('api.role.creation', role=serializer.instance)
895

  
896
    def perform_update(self, serializer):
897
        super().perform_update(serializer)
898
        self.request.journal.record('api.role.edit', role=serializer.instance, form=serializer)
879 899

  
880 900

  
881 901
class RolesMembersAPI(UsersAPI):
......
919 939
        if not request.user.has_perm('a2_rbac.manage_members_role', obj=self.role):
920 940
            raise PermissionDenied(u'User not allowed to manage role members')
921 941
        self.role.members.add(self.member)
942
        request.journal.record('api.role.membership.grant', role=self.role, member=self.member)
922 943
        return Response(
923 944
            {'result': 1, 'detail': _('User successfully added to role')}, status=status.HTTP_201_CREATED
924 945
        )
......
927 948
        if not request.user.has_perm('a2_rbac.manage_members_role', obj=self.role):
928 949
            raise PermissionDenied(u'User not allowed to manage role members')
929 950
        self.role.members.remove(self.member)
951
        request.journal.record('api.role.membership.removal', role=self.role, member=self.member)
930 952
        return Response(
931 953
            {'result': 1, 'detail': _('User successfully removed from role')}, status=status.HTTP_200_OK
932 954
        )
......
976 998

  
977 999
    def post(self, request, *args, **kwargs):
978 1000
        self.role.members.add(*self.members)
1001
        for member in self.members:
1002
            request.journal.record('api.role.membership.grant', role=self.role, member=member)
979 1003
        return Response(
980 1004
            {'result': 1, 'detail': _('Users successfully added to role')}, status=status.HTTP_201_CREATED
981 1005
        )
982 1006

  
983 1007
    def delete(self, request, *args, **kwargs):
984 1008
        self.role.members.remove(*self.members)
1009
        for member in self.members:
1010
            request.journal.record('api.role.membership.removal', role=self.role, member=member)
985 1011
        return Response(
986 1012
            {'result': 1, 'detail': _('Users successfully removed from role')}, status=status.HTTP_200_OK
987 1013
        )
988 1014

  
989 1015
    def patch(self, request, *args, **kwargs):
1016
        old_members = set(self.role.members.all())
990 1017
        self.role.members.set(self.members)
1018
        for member in self.members:
1019
            request.journal.record('api.role.membership.grant', role=self.role, member=member)
1020
        for member in old_members.difference(self.members):
1021
            request.journal.record('api.role.membership.removal', role=self.role, member=member)
991 1022
        return Response(
992 1023
            {'result': 1, 'detail': _('Users successfully assigned to role')}, status=status.HTTP_200_OK
993 1024
        )
src/authentic2/apps/journal/models.py
88 88
    def record(cls, user=None, session=None, references=None, data=None):
89 89
        event_type = EventType.objects.get_for_name(cls.name)
90 90

  
91
        if user and user.is_anonymous:
92
            user = None
93

  
91 94
        Event.objects.create(
92 95
            type=event_type,
93 96
            user=user,
src/authentic2/journal_event_types.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
from django.contrib.auth import get_user_model
17 18
from django.contrib.contenttypes.models import ContentType
18 19
from django.utils.translation import ugettext_lazy as _
19 20

  
20 21
from authentic2.apps.journal.models import EventTypeDefinition, n_2_pairing_rev
21 22
from authentic2.apps.journal.utils import Statistics, form_to_old_new
22 23
from authentic2.custom_user.models import User, get_attributes_map
24
from django_rbac.utils import get_role_model
23 25

  
24 26
from .models import Service
25 27

  
28
Role = get_role_model()
29
User = get_user_model()
30

  
26 31

  
27 32
class EventTypeWithService(EventTypeDefinition):
28 33
    @classmethod
......
46 51
        return ''
47 52

  
48 53

  
54
class RoleEventsMixin(EventTypeDefinition):
55
    @classmethod
56
    def record(self, user, role, session=None, references=None, data=None):
57
        references = references or []
58
        references = [role] + references
59
        data = data or {}
60
        data.update({'role_name': str(role), 'role_uuid': role.uuid})
61
        super().record(
62
            user=user,
63
            session=session,
64
            references=references,
65
            data=data,
66
        )
67

  
68

  
49 69
class EventTypeWithHow(EventTypeWithService):
50 70
    @classmethod
51 71
    def record(cls, user, session, service, how):
......
364 384
        elif reason == 'old-source':
365 385
            return _('automatic deactivation because user was from an old LDAP source')
366 386
        return super().get_message(event, context)
387

  
388

  
389
class ApiUserCreation(EventTypeDefinition):
390
    name = 'api.user.creation'
391
    label = _('user creation')
392

  
393
    @classmethod
394
    def record(cls, user, target_user):
395
        super().record(user=user, references=[target_user])
396

  
397
    @classmethod
398
    def get_message(cls, event, context):
399
        (user,) = event.get_typed_references(User)
400
        if context and context == user:
401
            return _('creation from API')
402
        elif user:
403
            return _('creation of user "%s" from API') % user.get_full_name()
404
        return super().get_message(event, context)
405

  
406

  
407
class ApiUserProfileEdit(EventTypeDefinition):
408
    name = 'api.user.profile.edit'
409
    label = _('user profile edit')
410

  
411
    @classmethod
412
    def record(cls, user, serializer):
413
        changed_data = serializer.validated_data.copy()
414
        attributes = changed_data.pop('attributes', {})
415
        changed_data.update(attributes)
416
        super().record(user=user, references=[serializer.instance], data={'new': changed_data})
417

  
418
    @classmethod
419
    def get_message(cls, event, context):
420
        (user,) = event.get_typed_references(User)
421
        new = event.get_data('new') or {}
422
        edited_attributes = ', '.join(get_attributes_label(new)) or ''
423
        if context and context == user:
424
            return _('edit from API (%s)') % edited_attributes
425
        elif user:
426
            user_full_name = user.get_full_name()
427
            return _('edit of user "{0}" ({1}) from API').format(user_full_name, edited_attributes)
428
        return super().get_message(event, context)
429

  
430

  
431
class ApiUserPasswordChange(EventTypeDefinition):
432
    name = 'api.user.password.change'
433
    label = _('user password change')
434

  
435
    @classmethod
436
    def record(cls, user, serializer):
437
        super().record(
438
            user=user, references=[serializer.instance], data={'email': serializer.validated_data['email']}
439
        )
440

  
441
    @classmethod
442
    def get_message(cls, event, context):
443
        (user,) = event.get_typed_references(User)
444
        email = event.get_data('email')
445
        if context and context == user:
446
            return _('password change from API and notification by mail')
447
        elif user:
448
            user_full_name = user.get_full_name()
449
            return _('password change from API of user "%s" and notification by mail') % user_full_name
450
        return super().get_message(event, context)
451

  
452

  
453
class ApiUserPasswordResetRequest(EventTypeDefinition):
454
    name = 'api.user.password.reset.request'
455
    label = _('user password reset request')
456

  
457
    @classmethod
458
    def record(cls, user, target_user):
459
        super().record(user=user, references=[target_user], data={'email': target_user.email})
460

  
461
    @classmethod
462
    def get_message(cls, event, context):
463
        (user,) = event.get_typed_references(User)
464
        email = event.get_data('email')
465
        if context and context == user:
466
            return _('password reset request from API sent to "%s"') % email
467
        elif user:
468
            return _('password reset request from API of "{0}" sent to "{1}"').format(
469
                user.get_full_name(), email
470
            )
471
        return super().get_message(event, context)
472

  
473

  
474
class ApiUserDeletion(EventTypeDefinition):
475
    name = 'api.user.deletion'
476
    label = _('user deletion')
477

  
478
    @classmethod
479
    def record(cls, user, target_user):
480
        super().record(user=user, references=[target_user])
481

  
482
    @classmethod
483
    def get_message(cls, event, context):
484
        (user,) = event.get_typed_references(User)
485
        if context and context == user:
486
            return _('deletion from API')
487
        elif user:
488
            return _('deletion of user "%s" from API') % user.get_full_name()
489
        return super().get_message(event, context)
490

  
491

  
492
class ApiRoleCreation(RoleEventsMixin):
493
    name = 'api.role.creation'
494
    label = _('role creation')
495

  
496
    @classmethod
497
    def get_message(cls, event, context):
498
        (role,) = event.get_typed_references(Role)
499
        role = role or event.get_data('role_name')
500
        if context != role:
501
            return _('creation of role "%s" from API') % role
502
        else:
503
            return _('creation from API')
504

  
505

  
506
class ApiRoleEdit(RoleEventsMixin):
507
    name = 'api.role.edit'
508
    label = _('role edit')
509

  
510
    @classmethod
511
    def record(cls, user, role, serializer):
512
        super().record(user=user, role=role, data={'new': serializer.validated_data})
513

  
514
    @classmethod
515
    def get_message(cls, event, context):
516
        (role,) = event.get_typed_references(Role)
517
        role = role or event.get_data('role_name')
518
        new = event.get_data('new')
519
        edited_attributes = ', '.join(get_attributes_label(new)) or ''
520
        if context != role:
521
            return _('edit of role "{role}" from API ({change})').format(role=role, change=edited_attributes)
522
        else:
523
            return _('edit from API ({change})').format(change=edited_attributes)
524

  
525

  
526
class ApiRoleDeletion(RoleEventsMixin):
527
    name = 'api.role.deletion'
528
    label = _('role deletion')
529

  
530
    @classmethod
531
    def get_message(cls, event, context):
532
        (role,) = event.get_typed_references(Role)
533
        role = role or event.get_data('role_name')
534
        if context != role:
535
            return _('deletion of role "%s" from API') % role
536
        else:
537
            return _('deletion from API')
538

  
539

  
540
class ApiRoleMembershipGrant(RoleEventsMixin):
541
    name = 'api.role.membership.grant'
542
    label = _('role membership grant')
543

  
544
    @classmethod
545
    def record(cls, user, role, member):
546
        data = {'member_name': member.get_full_name()}
547
        super().record(user=user, role=role, references=[member], data=data)
548

  
549
    @classmethod
550
    def get_message(cls, event, context):
551
        role, member = event.get_typed_references(Role, User)
552
        member = member or event.get_data('member_name')
553
        role = role or event.get_data('role_name')
554
        if context == member:
555
            return _('membership grant in role "%s" from API') % role
556
        elif context == role:
557
            return _('membership grant to user "%s" from API') % member
558
        else:
559
            return _('membership grant to user "{member}" in role "{role}" from API').format(
560
                member=member, role=role
561
            )
562

  
563

  
564
class ApiRoleMembershipRemoval(RoleEventsMixin):
565
    name = 'api.role.membership.removal'
566
    label = _('role membership removal')
567

  
568
    @classmethod
569
    def record(cls, user, role, member):
570
        data = {'member_name': member.get_full_name()}
571
        super().record(user=user, role=role, references=[member], data=data)
572

  
573
    @classmethod
574
    def get_message(cls, event, context):
575
        role, member = event.get_typed_references(Role, User)
576
        member = member or event.get_data('member_name')
577
        role = role or event.get_data('role_name')
578
        if context == member:
579
            return _('membership removal from role "%s" from API') % role
580
        elif context == role:
581
            return _('membership removal of user "%s" from API') % member
582
        else:
583
            return _('membership removal of user "{member}" from role "{role}" from API').format(
584
                member=member, role=role
585
            )
src/authentic2/manager/journal_event_types.py
19 19

  
20 20
from authentic2.apps.journal.models import EventTypeDefinition
21 21
from authentic2.apps.journal.utils import form_to_old_new
22
from authentic2.journal_event_types import EventTypeWithService, get_attributes_label
22
from authentic2.journal_event_types import EventTypeWithService, RoleEventsMixin, get_attributes_label
23 23
from django_rbac.utils import get_role_model
24 24

  
25 25
User = get_user_model()
......
257 257
        return super().get_message(event, context)
258 258

  
259 259

  
260
class RoleEventsMixin(EventTypeDefinition):
261
    @classmethod
262
    def record(self, user, session, role, references=None, data=None):
263
        references = references or []
264
        references = [role] + references
265
        data = data or {}
266
        data.update({'role_name': str(role), 'role_uuid': role.uuid})
267
        super().record(
268
            user=user,
269
            session=session,
270
            references=references,
271
            data=data,
272
        )
273

  
274

  
275 260
class ManagerRoleCreation(RoleEventsMixin):
276 261
    name = 'manager.role.creation'
277 262
    label = _('role creation')
src/authentic2/manager/journal_views.py
93 93
            ('manager.role', _('Role management')),
94 94
        ),
95 95
    ),
96
    (
97
        _('API'),
98
        (
99
            ('api', _('All')),
100
            ('api.user', _('User management')),
101
            ('api.role', _('Role management')),
102
        ),
103
    ),
96 104
)
97 105

  
98 106

  
tests/test_manager_journal.py
256 256
        user=user,
257 257
        reason='not-present',
258 258
    )
259
    make('api.user.creation', user=agent, target_user=user)
260
    user_serializer = mock.Mock(spec=['instance', 'validated_data'])
261
    user_serializer.instance = user
262
    user_serializer.validated_data = {'email': 'jane@example.com', 'attributes': {'city': 'paris'}}
263
    make('api.user.profile.edit', user=agent, serializer=user_serializer)
264
    make('api.user.password.change', user=agent, serializer=user_serializer)
265
    user_serializer = mock.Mock(spec=['instance', 'validated_data'])
266
    user_serializer.instance = user
267
    user_serializer.validated_data = {'email': 'jane@example.com'}
268
    make('api.user.profile.edit', user=agent, serializer=user_serializer)
269
    make('api.user.password.reset.request', user=agent, target_user=user)
270
    make('api.user.deletion', user=agent, target_user=user)
271
    make('api.role.creation', user=agent, role=role_user)
272
    make('api.role.deletion', user=agent, role=role_user)
273
    role_serializer = mock.Mock(spec=['validated_data'])
274
    role_serializer.validated_data = {'name': 'test'}
275
    make('api.role.edit', user=agent, role=role_user, serializer=role_serializer)
276
    make("api.role.membership.grant", user=agent, role=role_user, member=user)
277
    make("api.role.membership.removal", user=agent, role=role_user, member=user)
259 278

  
260 279
    # verify we created at least one event for each type
261 280
    assert set(Event.objects.values_list("type__name", flat=True)) == set(_registry)
......
553 572
            'type': 'ldap.user.deactivation',
554 573
            'user': 'Johnny doe',
555 574
        },
575
        {
576
            'timestamp': 'Jan. 2, 2020, 6 p.m.',
577
            'type': 'api.user.creation',
578
            'user': 'agent',
579
            'message': 'creation of user "Johnny doe" from API',
580
        },
581
        {
582
            'timestamp': 'Jan. 2, 2020, 7 p.m.',
583
            'type': 'api.user.profile.edit',
584
            'user': 'agent',
585
            'message': 'edit of user "Johnny doe" (city, email address) from API',
586
        },
587
        {
588
            'timestamp': 'Jan. 2, 2020, 8 p.m.',
589
            'type': 'api.user.password.change',
590
            'user': 'agent',
591
            'message': 'password change from API of user "Johnny doe" and notification by mail',
592
        },
593
        {
594
            'timestamp': 'Jan. 2, 2020, 9 p.m.',
595
            'type': 'api.user.profile.edit',
596
            'user': 'agent',
597
            'message': 'edit of user "Johnny doe" (email address) from API',
598
        },
599
        {
600
            'timestamp': 'Jan. 2, 2020, 10 p.m.',
601
            'type': 'api.user.password.reset.request',
602
            'user': 'agent',
603
            'message': 'password reset request from API of "Johnny doe" sent to "user@example.com"',
604
        },
605
        {
606
            'timestamp': 'Jan. 2, 2020, 11 p.m.',
607
            'type': 'api.user.deletion',
608
            'user': 'agent',
609
            'message': 'deletion of user "Johnny doe" from API',
610
        },
611
        {
612
            'timestamp': 'Jan. 3, 2020, midnight',
613
            'type': 'api.role.creation',
614
            'user': 'agent',
615
            'message': 'creation of role "role1" from API',
616
        },
617
        {
618
            'timestamp': 'Jan. 3, 2020, 1 a.m.',
619
            'type': 'api.role.deletion',
620
            'user': 'agent',
621
            'message': 'deletion of role "role1" from API',
622
        },
623
        {
624
            'timestamp': 'Jan. 3, 2020, 2 a.m.',
625
            'type': 'api.role.edit',
626
            'user': 'agent',
627
            'message': 'edit of role "role1" from API (name)',
628
        },
629
        {
630
            'timestamp': 'Jan. 3, 2020, 3 a.m.',
631
            'type': 'api.role.membership.grant',
632
            'user': 'agent',
633
            'message': 'membership grant to user "user (111111)" in role "role1" from API',
634
        },
635
        {
636
            'timestamp': 'Jan. 3, 2020, 4 a.m.',
637
            'type': 'api.role.membership.removal',
638
            'user': 'agent',
639
            'message': 'membership removal of user "user (111111)" from role "role1" from API',
640
        },
556 641
    ]
557 642

  
558 643

  
......
744 829
            'type': 'ldap.user.deactivation',
745 830
            'user': 'Johnny doe',
746 831
        },
832
        {
833
            'timestamp': 'Jan. 2, 2020, 6 p.m.',
834
            'type': 'api.user.creation',
835
            'user': 'agent',
836
            'message': 'creation from API',
837
        },
838
        {
839
            'timestamp': 'Jan. 2, 2020, 7 p.m.',
840
            'type': 'api.user.profile.edit',
841
            'user': 'agent',
842
            'message': 'edit from API (city, email address)',
843
        },
844
        {
845
            'timestamp': 'Jan. 2, 2020, 8 p.m.',
846
            'type': 'api.user.password.change',
847
            'user': 'agent',
848
            'message': 'password change from API and notification by mail',
849
        },
850
        {
851
            'timestamp': 'Jan. 2, 2020, 9 p.m.',
852
            'type': 'api.user.profile.edit',
853
            'user': 'agent',
854
            'message': 'edit from API (email address)',
855
        },
856
        {
857
            'timestamp': 'Jan. 2, 2020, 10 p.m.',
858
            'type': 'api.user.password.reset.request',
859
            'user': 'agent',
860
            'message': 'password reset request from API sent to "user@example.com"',
861
        },
862
        {
863
            'timestamp': 'Jan. 2, 2020, 11 p.m.',
864
            'type': 'api.user.deletion',
865
            'user': 'agent',
866
            'message': 'deletion from API',
867
        },
868
        {
869
            'timestamp': 'Jan. 3, 2020, 3 a.m.',
870
            'type': 'api.role.membership.grant',
871
            'user': 'agent',
872
            'message': 'membership grant in role "role1" from API',
873
        },
874
        {
875
            'timestamp': 'Jan. 3, 2020, 4 a.m.',
876
            'type': 'api.role.membership.removal',
877
            'user': 'agent',
878
            'message': 'membership removal from role "role1" from API',
879
        },
747 880
    ]
748 881

  
749 882

  
750
-