0003-api-add-a-user-profile-management-endpoint-58554.patch
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 |
- |