Projet

Général

Profil

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

Paul Marillonnet, 06 janvier 2022 15:01

Télécharger (9,42 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 | 80 ++++++++++++++++++++++++++++++++-
 tests/test_api.py           | 90 +++++++++++++++++++++++++++++++++++++
 3 files changed, 174 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>[^/]+)/$',
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 = OrganizationalUnit
968
        fields = ('data',)
969

  
970

  
971
class UserProfilesAPI(ExceptionHandlerMixin, APIView):
972
    permissions_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
        return Response(self.serializer_class(profile).data)
984

  
985
    def post(self, request, *args, **kwargs):
986
        serializer = self.serializer_class(data=request.data)
987
        if not serializer.is_valid():
988
            response = {'data': [], 'err': 1, 'err_desc': serializer.errors}
989
            return Response(response, status.HTTP_400_BAD_REQUEST)
990
        try:
991
            profile = Profile.objects.get(user=self.user, profile_type=self.profile_type)
992
        except Profile.DoesNotExist:
993
            profile = serializer.save()
994
            profile.profile_type = self.profile_type
995
            profile.user = self.user
996
            profile.save()  # fixme double db access
997
            request.journal.record(
998
                'user.profile.add', user=self.user, profile_type=self.profile_type, api=True
999
            )
1000
            return Response(
1001
                {'result': 1, 'detail': _('Profile successfully assigned to user')}, status=status.HTTP_200_OK
1002
            )
1003

  
1004
    def patch(self, request, *args, **kwargs):
1005
        serializer = self.serializer_class(data=request.data)
1006
        if not serializer.is_valid():
1007
            response = {'data': [], 'err': 1, 'err_desc': serializer.errors}
1008
            return Response(response, status.HTTP_400_BAD_REQUEST)
1009
        profile = get_object_or_404(Profile, user=self.user, profile_type=self.profile_type)
1010
        profile.data = request.data.get('data', {})
1011
        profile.save()
1012
        request.journal.record(
1013
            'user.profile.update', user=self.user, profile_type=self.profile_type, api=True
1014
        )
1015
        return Response({'result': 1, 'detail': _('Profile successfully updated')}, status=status.HTTP_200_OK)
1016

  
1017
    def put(self, request, *args, **kwargs):
1018
        return self.patch(request, *args, **kwargs)
1019

  
1020
    def delete(self, request, *args, **kwargs):
1021
        profile = get_object_or_404(Profile, user=self.user, profile_type=self.profile_type)
1022
        request.journal.record(
1023
            'user.profile.delete', user=self.user, profile_type=self.profile_type, api=True
1024
        )
1025
        profile.delete()
1026
        return Response(
1027
            {'result': 1, 'detail': _('Profile successfully removed from user')}, status=status.HTTP_200_OK
1028
        )
1029

  
1030

  
1031
user_profiles = UserProfilesAPI.as_view()
1032

  
1033

  
956 1034
class RoleMembershipAPI(ExceptionHandlerMixin, APIView):
957 1035
    permission_classes = (permissions.IsAuthenticated,)
958 1036

  
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
......
2616 2617

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

  
2621

  
2622
def test_user_profile_get(app, admin):
2623
    app.authorization = ('Basic', (admin.username, admin.username))
2624

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

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

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

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

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

  
2639

  
2640
def test_user_profile_put(app, admin):
2641
    app.authorization = ('Basic', (admin.username, admin.username))
2642

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

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

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

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

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

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

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

  
2665
    # attempt at overwriting profile data
2666
    resp = app.put_json('/api/users/%s/profiles/one-type/' % user.uuid, params={'data': {'baz': 'bob'}})
2667

  
2668
    # profile has been updated, no extra profile has been created
2669
    assert resp.json == {'result': 1, 'detail': 'Profile successfully updated'}
2670
    assert len(user.subprofiles.all()) == 1
2671
    assert user.subprofiles.last().data == {'baz': 'bob'}
2672

  
2673

  
2674
def test_user_profile_post(app, admin):
2675
    app.authorization = ('Basic', (admin.username, admin.username))
2676

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

  
2679
    # wrong type
2680
    resp = app.get('/api/users/%s/profiles/non-existant-slug/' % user.uuid, status=404)
2681

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

  
2684
    # wrong user
2685
    resp = app.get('/api/users/1234/profiles/one-type/', status=404)
2686

  
2687
    assert len(user.subprofiles.all()) == 0
2688

  
2689
    resp = app.post_json('/api/users/%s/profiles/one-type/' % user.uuid, params={'data': {'baz': 'bob'}})
2690
    assert resp.json == {'result': 1, 'detail': 'Profile successfully assigned to user'}
2691
    assert len(user.subprofiles.all()) == 1
2692
    assert user.subprofiles.last().data == {'baz': 'bob'}
2693

  
2694

  
2695
def test_user_profile_delete(app, admin):
2696

  
2697
    app.authorization = ('Basic', (admin.username, admin.username))
2698

  
2699
    user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou())
2700
    profile_type = ProfileType.objects.create(slug='one-type', name='One Type')
2701
    Profile.objects.create(user=user, profile_type=profile_type)
2702

  
2703
    resp = app.delete('/api/users/%s/profiles/non-existant-slug/' % user.uuid, status=404)
2704
    resp = app.delete('/api/users/%s/profiles/one-type/' % user.uuid)
2705

  
2706
    assert not Profile.objects.filter(user=user, profile_type=profile_type)
2707

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