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>[^/]+)/$', |
|
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 |
- |