Projet

Général

Profil

0003-api-add-a-user-profile-management-endpoint-58554.patch

Paul Marillonnet, 21 février 2022 17:30

Télécharger (10,3 ko)

Voir les différences:

Subject: [PATCH 3/3] api: add a user profile management endpoint (#58554)

 src/authentic2/api_urls.py  |  5 ++
 src/authentic2/api_views.py | 94 +++++++++++++++++++++++++++++++++++-
 tests/test_api.py           | 96 +++++++++++++++++++++++++++++++++++++
 3 files changed, 194 insertions(+), 1 deletion(-)
src/authentic2/api_urls.py
32 32
        api_views.role_membership,
33 33
        name='a2-api-role-member',
34 34
    ),
35
    url(
36
        r'^users/(?P<user_uuid>[\w+]*)/profiles/(?P<profile_type_slug>[^/]+)/default/$',
37
        api_views.user_profiles,
38
        name='a2-api-user-profiles',
39
    ),
35 40
    url(
36 41
        r'^roles/(?P<role_uuid>[\w+]*)/relationships/members/$',
37 42
        api_views.role_memberships,
src/authentic2/api_views.py
57 57
from . import api_mixins, app_settings, decorators, hooks
58 58
from .a2_rbac.models import OrganizationalUnit, Role
59 59
from .a2_rbac.utils import get_default_ou
60
from .custom_user.models import User
60
from .custom_user.models import Profile, ProfileType, User
61 61
from .journal_event_types import UserLogin, UserRegistration
62 62
from .models import Attribute, PasswordReset, Service
63 63
from .passwords import get_password_checker
......
953 953
roles_members = RolesMembersAPI.as_view({'get': 'list'})
954 954

  
955 955

  
956
class ProfileSerializer(serializers.ModelSerializer):
957
    data = serializers.JSONField(binary=False)
958

  
959
    def create(self, validated_data):
960
        return Profile(**validated_data)
961

  
962
    def update(self, validated_data):
963
        # not supported yet
964
        pass
965

  
966
    class Meta:
967
        model = Profile
968
        fields = ('data',)
969

  
970

  
971
class UserProfilesAPI(ExceptionHandlerMixin, APIView):
972
    permission_classes = (permissions.IsAuthenticated,)
973
    serializer_class = ProfileSerializer
974

  
975
    def initial(self, request, *args, **kwargs):
976
        super().initial(request, *args, **kwargs)
977
        User = get_user_model()
978
        self.profile_type = get_object_or_404(ProfileType, slug=kwargs['profile_type_slug'])
979
        self.user = get_object_or_404(User, uuid=kwargs['user_uuid'])
980

  
981
    def get(self, request, *args, **kwargs):
982
        profile = get_object_or_404(Profile, user=self.user, profile_type=self.profile_type)
983
        if not request.user.has_perm('custom_user.view_profile', obj=profile):
984
            raise PermissionDenied('User not allowed to view user profiles')
985
        return Response(self.serializer_class(profile).data)
986

  
987
    def post(self, request, *args, **kwargs):
988
        if not request.user.has_perm('custom_user.create_profile'):
989
            raise PermissionDenied('User not allowed to create user profiles')
990
        serializer = self.serializer_class(data=request.data)
991
        if not serializer.is_valid():
992
            response = {'data': [], 'err': 1, 'err_desc': serializer.errors}
993
            return Response(response, status.HTTP_400_BAD_REQUEST)
994
        try:
995
            profile = Profile.objects.get(user=self.user, profile_type=self.profile_type)
996
        except Profile.DoesNotExist:
997
            profile = serializer.save()
998
            profile.profile_type = self.profile_type
999
            profile.user = self.user
1000
            profile.save()  # fixme double db access
1001
            request.journal.record(
1002
                'user.profile.add',
1003
                user=request.user,
1004
                profile=profile,
1005
            )
1006
            return Response(
1007
                {'result': 1, 'detail': _('Profile successfully assigned to user')}, status=status.HTTP_200_OK
1008
            )
1009

  
1010
    def patch(self, request, *args, **kwargs):
1011
        serializer = self.serializer_class(data=request.data)
1012
        if not serializer.is_valid():
1013
            response = {'data': [], 'err': 1, 'err_desc': serializer.errors}
1014
            return Response(response, status.HTTP_400_BAD_REQUEST)
1015
        profile = get_object_or_404(Profile, user=self.user, profile_type=self.profile_type)
1016
        if not request.user.has_perm('custom_user.change_profile', obj=profile):
1017
            raise PermissionDenied('User not allowed to change user profiles')
1018
        profile.data = request.data.get('data', {})
1019
        profile.save()
1020
        request.journal.record(
1021
            'user.profile.update',
1022
            user=request.user,
1023
            profile=profile,
1024
        )
1025
        return Response({'result': 1, 'detail': _('Profile successfully updated')}, status=status.HTTP_200_OK)
1026

  
1027
    def put(self, request, *args, **kwargs):
1028
        return self.patch(request, *args, **kwargs)
1029

  
1030
    def delete(self, request, *args, **kwargs):
1031
        profile = get_object_or_404(Profile, user=self.user, profile_type=self.profile_type)
1032
        if not request.user.has_perm('custom_user.delete_profile', obj=profile):
1033
            raise PermissionDenied('User not allowed to delete user profiles')
1034
        request.journal.record(
1035
            'user.profile.delete',
1036
            user=request.user,
1037
            profile=profile,
1038
        )
1039
        profile.delete()
1040
        return Response(
1041
            {'result': 1, 'detail': _('Profile successfully removed from user')}, status=status.HTTP_200_OK
1042
        )
1043

  
1044

  
1045
user_profiles = UserProfilesAPI.as_view()
1046

  
1047

  
956 1048
class RoleMembershipAPI(ExceptionHandlerMixin, APIView):
957 1049
    permission_classes = (permissions.IsAuthenticated,)
958 1050

  
tests/test_api.py
36 36
from authentic2.a2_rbac.models import Role
37 37
from authentic2.a2_rbac.utils import get_default_ou
38 38
from authentic2.apps.journal.models import Event, EventType
39
from authentic2.custom_user.models import Profile, ProfileType
39 40
from authentic2.models import Attribute, AttributeValue, AuthorizedRole, PasswordReset, Service
40 41
from authentic2.utils.misc import good_next_url
41 42
from django_rbac.models import SEARCH_OP
......
2617 2618

  
2618 2619
    resp = app.get('/api/users/?limit=1')
2619 2620
    assert len(resp.json['results']) == 1
2621

  
2622

  
2623
def test_user_profile_get(app, superuser):
2624
    app.authorization = ('Basic', (superuser.username, superuser.username))
2625

  
2626
    user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou())
2627

  
2628
    # wrong type
2629
    resp = app.get('/api/users/%s/profiles/non-existant-slug/default/' % user.uuid, status=404)
2630

  
2631
    profile_type = ProfileType.objects.create(slug='one-type', name='One Type')
2632

  
2633
    # wrong user
2634
    resp = app.get('/api/users/1234/profiles/one-type/default/', status=404)
2635

  
2636
    Profile.objects.create(profile_type=profile_type, user=user, data={'foo': 'bar'})
2637
    resp = app.get('/api/users/%s/profiles/one-type/default/' % user.uuid)
2638
    assert resp.json == {'data': {'foo': 'bar'}}
2639

  
2640

  
2641
def test_user_profile_put(app, superuser):
2642
    app.authorization = ('Basic', (superuser.username, superuser.username))
2643

  
2644
    user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou())
2645

  
2646
    # wrong type
2647
    resp = app.put('/api/users/%s/profiles/non-existant-slug/default/' % user.uuid, status=404)
2648

  
2649
    profile_type = ProfileType.objects.create(slug='one-type', name='One Type')
2650

  
2651
    # wrong user
2652
    resp = app.put('/api/users/1234/profiles/one-type/default/', status=404)
2653

  
2654
    # missing profile
2655
    resp = app.put_json(
2656
        '/api/users/%s/profiles/one-type/default/' % user.uuid, params={'data': {'foo': 'bar'}}, status=404
2657
    )
2658

  
2659
    Profile.objects.create(user=user, profile_type=profile_type, data={})
2660

  
2661
    resp = app.put_json(
2662
        '/api/users/%s/profiles/one-type/default/' % user.uuid, params={'data': {'foo': 'bar'}}
2663
    )
2664
    assert resp.json == {'result': 1, 'detail': 'Profile successfully updated'}
2665
    assert len(user.subprofiles.all()) == 1
2666
    assert user.subprofiles.last().data == {'foo': 'bar'}
2667

  
2668
    # attempt at overwriting profile data
2669
    resp = app.put_json(
2670
        '/api/users/%s/profiles/one-type/default/' % user.uuid, params={'data': {'baz': 'bob'}}
2671
    )
2672

  
2673
    # profile has been updated, no extra profile has been created
2674
    assert resp.json == {'result': 1, 'detail': 'Profile successfully updated'}
2675
    assert len(user.subprofiles.all()) == 1
2676
    assert user.subprofiles.last().data == {'baz': 'bob'}
2677

  
2678

  
2679
def test_user_profile_post(app, superuser):
2680
    app.authorization = ('Basic', (superuser.username, superuser.username))
2681

  
2682
    user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou())
2683

  
2684
    # wrong type
2685
    resp = app.get('/api/users/%s/profiles/non-existant-slug/default/' % user.uuid, status=404)
2686

  
2687
    ProfileType.objects.create(slug='one-type', name='One Type')
2688

  
2689
    # wrong user
2690
    resp = app.get('/api/users/1234/profiles/one-type/default/', status=404)
2691

  
2692
    assert len(user.subprofiles.all()) == 0
2693

  
2694
    resp = app.post_json(
2695
        '/api/users/%s/profiles/one-type/default/' % user.uuid, params={'data': {'baz': 'bob'}}
2696
    )
2697
    assert resp.json == {'result': 1, 'detail': 'Profile successfully assigned to user'}
2698
    assert len(user.subprofiles.all()) == 1
2699
    assert user.subprofiles.last().data == {'baz': 'bob'}
2700

  
2701

  
2702
def test_user_profile_delete(app, superuser):
2703

  
2704
    app.authorization = ('Basic', (superuser.username, superuser.username))
2705

  
2706
    user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou())
2707
    profile_type = ProfileType.objects.create(slug='one-type', name='One Type')
2708
    Profile.objects.create(user=user, profile_type=profile_type)
2709

  
2710
    app.delete('/api/users/%s/profiles/non-existant-slug/default/' % user.uuid, status=404)
2711
    app.delete('/api/users/%s/profiles/one-type/default/' % user.uuid)
2712

  
2713
    assert not Profile.objects.filter(user=user, profile_type=profile_type)
2714

  
2715
    app.delete('/api/users/%s/profiles/one-type/default/' % user.uuid, status=404)
2620
-