Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Affiche les permissions dans les réponses de l'API #3264

Merged
merged 2 commits into from Jan 13, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions requirements.txt
Expand Up @@ -30,6 +30,7 @@ django-oauth-toolkit==0.9.0
drf-extensions==0.2.7
django-rest-swagger==0.2.9
django-cors-headers==1.0.0
dry-rest-permissions==0.1.5
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je vois que la 0.1.6 est sortie :)


# Zep 12 dependency
django-uuslug==1.0.3
8 changes: 7 additions & 1 deletion zds/member/api/generics.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-

from django.conf import settings
from dry_rest_permissions.generics import DRYPermissions
from rest_framework import status
from rest_framework.generics import CreateAPIView, DestroyAPIView
from rest_framework.permissions import IsAuthenticated
Expand All @@ -18,7 +19,6 @@ class CreateDestroyMemberSanctionAPIView(CreateAPIView, DestroyAPIView):

queryset = Profile.objects.all()
serializer_class = ProfileSanctionSerializer
permission_classes = (IsAuthenticated, IsStaffUser)

def post(self, request, *args, **kwargs):
return self.process_request(request)
Expand Down Expand Up @@ -55,5 +55,11 @@ def process_request(self, request):
state.notify_member(ban, msg)
return Response(serializer.data)

def get_permissions(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

peux-tu ajouter un docstring pour qu'on comprenne ce qui est vraiment retourné stp? Car pour quelqu'un qui ne connait pas bien le système c'est pas immédiat. (après c'est pas obligatoire)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Est-ce nécessaire ? Ce sont des méthodes surchargées avec une documentation au niveau de la méthode parente. Je peux le faire mais je vais me contenter de faire un copier/coller de la documentation officielle de la bibliothèque (chose que je ne trouve pas très pertinent).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

De plus, get_permissions me semble quand même explicite comme nom.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Donc effectivement, ce n'était pas nécessaire.

Le 02/01/2016 18:38, Gérard Paligot a écrit :

In zds/member/api/generics.py
#3264 (comment):

@@ -55,5 +55,11 @@ def process_request(self, request):
state.notify_member(ban, msg)
return Response(serializer.data)

  • def get_permissions(self):

Est-ce nécessaire ? Ce sont des méthodes surchargées avec une
documentation au niveau de la méthode parente. Je peux le faire mais
je vais me contenter de faire un copier/coller de la documentation
officielle de la bibliothèque (chose que je ne trouve pas très pertinent).


Reply to this email directly or view it on GitHub
https://github.com/zestedesavoir/zds-site/pull/3264/files#r48686057.

permission_classes = [IsAuthenticated, IsStaffUser, ]
if self.request.method == 'POST' or self.request.method == 'DELETE':
permission_classes.append(DRYPermissions)
return [permission() for permission in permission_classes]

def get_state_instance(self, request):
raise NotImplementedError('`get_state_instance()` must be implemented.')
18 changes: 12 additions & 6 deletions zds/member/api/serializers.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from django.contrib.auth.models import User

from dry_rest_permissions.generics import DRYPermissionsField
from rest_framework import serializers

from zds.member.commons import ProfileCreate
Expand Down Expand Up @@ -33,10 +33,11 @@ class ProfileListSerializer(serializers.ModelSerializer):
is_active = serializers.BooleanField(source='user.is_active')
date_joined = serializers.DateTimeField(source='user.date_joined')
avatar_url = serializers.CharField(source='get_avatar_url')
permissions = DRYPermissionsField(additional_actions=['ban'])

class Meta:
model = Profile
fields = ('id', 'username', 'is_active', 'date_joined', 'avatar_url')
fields = ('id', 'username', 'is_active', 'date_joined', 'avatar_url', 'permissions')


class ProfileCreateSerializer(serializers.ModelSerializer, ProfileCreate, ProfileUsernameValidator,
Expand All @@ -49,10 +50,11 @@ class ProfileCreateSerializer(serializers.ModelSerializer, ProfileCreate, Profil
username = serializers.CharField(source='user.username')
email = serializers.EmailField(source='user.email')
password = serializers.CharField(source='user.password')
permissions = DRYPermissionsField(additional_actions=['ban'])

class Meta:
model = Profile
fields = ('id', 'username', 'email', 'password')
fields = ('id', 'username', 'email', 'password', 'permissions')
write_only_fields = ('password')

def create(self, validated_data):
Expand All @@ -75,12 +77,14 @@ class ProfileDetailSerializer(serializers.ModelSerializer):
is_active = serializers.BooleanField(source='user.is_active')
date_joined = serializers.DateTimeField(source='user.date_joined')
avatar_url = serializers.CharField(source='get_avatar_url')
permissions = DRYPermissionsField(additional_actions=['ban'])

class Meta:
model = Profile
fields = ('id', 'username', 'email', 'is_active', 'date_joined',
'site', 'avatar_url', 'biography', 'sign', 'show_email',
'show_sign', 'hover_or_click', 'allow_temp_visual_changes', 'email_for_answer', 'last_visit')
'show_sign', 'hover_or_click', 'allow_temp_visual_changes',
'email_for_answer', 'last_visit', 'permissions')

def __init__(self, *args, **kwargs):
"""
Expand All @@ -106,13 +110,15 @@ class ProfileValidatorSerializer(serializers.ModelSerializer, ProfileUsernameVal
email = serializers.EmailField(source='user.email', required=False, allow_blank=True)
is_active = serializers.BooleanField(source='user.is_active', required=False)
date_joined = serializers.DateTimeField(source='user.date_joined', required=False)
permissions = DRYPermissionsField(additional_actions=['ban'])

class Meta:
model = Profile
fields = ('id', 'username', 'email', 'is_active', 'date_joined',
'site', 'avatar_url', 'biography', 'sign', 'show_email',
'show_sign', 'hover_or_click', 'email_for_answer', 'last_visit')
read_only_fields = ('is_active', 'date_joined', 'last_visit',)
'show_sign', 'hover_or_click', 'email_for_answer', 'last_visit',
'permissions')
read_only_fields = ('is_active', 'date_joined', 'last_visit', 'permissions',)

def update(self, instance, validated_data):
"""
Expand Down
128 changes: 128 additions & 0 deletions zds/member/api/tests.py
Expand Up @@ -993,6 +993,134 @@ def test_staff_apply_ban_on_self(self):
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)


class PermissionMemberAPITest(APITestCase):
def setUp(self):
self.profile = ProfileFactory()
self.staff = StaffProfileFactory()

get_cache(extensions_api_settings.DEFAULT_USE_CACHE).clear()

def test_has_read_permission_for_anonymous_users(self):
"""
Anonymous users have the permission to read any member.
"""
response = self.client.get(reverse('api-member-detail', args=[self.profile.user.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data.get('permissions').get('read'))

def test_has_read_permission_for_authenticated_users(self):
"""
Authenticated users have the permission to read any member.
"""
authenticate_client(self.client, create_oauth2_client(self.profile.user),
self.profile.user.username, 'hostel77')

response = self.client.get(reverse('api-member-detail', args=[self.profile.user.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data.get('permissions').get('read'))

def test_has_read_permission_for_staff_users(self):
"""
Staff users have the permission to read any member.
"""
authenticate_client(self.client, create_oauth2_client(self.staff.user),
self.staff.user.username, 'hostel77')

response = self.client.get(reverse('api-member-detail', args=[self.profile.user.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data.get('permissions').get('read'))

def test_has_not_write_permission_for_anonymous_user(self):
"""
An anonymous user haven't write permissions.
"""
response = self.client.get(reverse('api-member-detail', args=[self.profile.user.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(response.data.get('permissions').get('write'))

def test_has_write_permission_for_authenticated_user(self):
"""
A user authenticated have write permissions.
"""
authenticate_client(self.client, create_oauth2_client(self.profile.user),
self.profile.user.username, 'hostel77')

response = self.client.get(reverse('api-member-detail', args=[self.profile.user.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data.get('permissions').get('write'))

def test_has_write_permission_for_staff(self):
"""
A staff user have write permissions.
"""
authenticate_client(self.client, create_oauth2_client(self.staff.user),
self.staff.user.username, 'hostel77')

response = self.client.get(reverse('api-member-detail', args=[self.profile.user.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data.get('permissions').get('write'))

def test_has_not_update_permission_for_anonymous_user(self):
"""
Only the user authenticated have update permissions.
"""
response = self.client.get(reverse('api-member-detail', args=[self.profile.user.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(response.data.get('permissions').get('update'))

def test_has_update_permission_for_authenticated_user(self):
"""
Only the user authenticated have update permissions.
"""
authenticate_client(self.client, create_oauth2_client(self.profile.user),
self.profile.user.username, 'hostel77')

response = self.client.get(reverse('api-member-detail', args=[self.profile.user.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data.get('permissions').get('update'))

def test_has_not_update_permission_for_staff(self):
"""
Only the user authenticated have update permissions.
"""
authenticate_client(self.client, create_oauth2_client(self.staff.user),
self.staff.user.username, 'hostel77')

response = self.client.get(reverse('api-member-detail', args=[self.profile.user.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(response.data.get('permissions').get('update'))

def test_has_not_ban_permission_for_anonymous_user(self):
"""
Only staff have ban permission.
"""
response = self.client.get(reverse('api-member-detail', args=[self.profile.user.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(response.data.get('permissions').get('ban'))

def test_has_not_ban_permission_for_authenticated_user(self):
"""
Only staff have ban permission.
"""
authenticate_client(self.client, create_oauth2_client(self.profile.user),
self.profile.user.username, 'hostel77')

response = self.client.get(reverse('api-member-detail', args=[self.profile.user.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(response.data.get('permissions').get('ban'))

def test_has_ban_permission_for_staff(self):
"""
Only staff have ban permission.
"""
authenticate_client(self.client, create_oauth2_client(self.staff.user),
self.staff.user.username, 'hostel77')

response = self.client.get(reverse('api-member-detail', args=[self.profile.user.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data.get('permissions').get('ban'))


def create_oauth2_client(user):
client = Application.objects.create(user=user,
client_type=Application.CLIENT_CONFIDENTIAL,
Expand Down
29 changes: 24 additions & 5 deletions zds/member/api/views.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-

from dry_rest_permissions.generics import DRYPermissions
from rest_framework import filters
from rest_framework import status
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateAPIView, RetrieveAPIView, get_object_or_404
Expand Down Expand Up @@ -85,8 +85,7 @@ def post(self, request, *args, **kwargs):
- code: 400
message: Bad Request
"""
self.permission_classes = (AllowAny,)
serializer = self.get_serializer_class()(data=request.data)
serializer = self.get_serializer_class()(data=request.data, context={'request': self.request})
serializer.is_valid(raise_exception=True)
profile = serializer.save()
token = self.generate_token(profile.user)
Expand All @@ -100,13 +99,18 @@ def get_serializer_class(self):
elif self.request.method == 'POST':
return ProfileCreateSerializer

def get_permissions(self):
permission_classes = [AllowAny, ]
if self.request.method == 'GET' or self.request.method == 'POST':
permission_classes.append(DRYPermissions)
return [permission() for permission in permission_classes]


class MemberMyDetailAPI(RetrieveAPIView):
"""
Profile resource to display details of the member.
"""
obj_key_func = MyDetailKeyConstructor()
permission_classes = (IsAuthenticated,)
serializer_class = ProfileDetailSerializer

@etag(obj_key_func)
Expand Down Expand Up @@ -134,6 +138,12 @@ def get(self, request, *args, **kwargs):
def get_object(self):
return get_object_or_404(Profile, user=self.request.user)

def get_permissions(self):
permission_classes = [IsAuthenticated, ]
if self.request.method == 'GET':
permission_classes.append(DRYPermissions)
return [permission() for permission in permission_classes]


class MemberDetailAPI(RetrieveUpdateAPIView):
"""
Expand Down Expand Up @@ -187,7 +197,6 @@ def put(self, request, *args, **kwargs):
- code: 404
message: Not found
"""
self.permission_classes = (IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
return self.update(request, *args, **kwargs)

def get_serializer_class(self):
Expand All @@ -196,6 +205,16 @@ def get_serializer_class(self):
elif self.request.method == 'PUT':
return ProfileValidatorSerializer

def get_permissions(self):
permission_classes = []
if self.request.method == 'GET':
permission_classes.append(DRYPermissions)
elif self.request.method == 'PUT':
permission_classes.append(DRYPermissions)
permission_classes.append(IsAuthenticatedOrReadOnly)
permission_classes.append(IsOwnerOrReadOnly)
return [permission() for permission in permission_classes]


class MemberDetailReadingOnly(CreateDestroyMemberSanctionAPIView):
"""
Expand Down
24 changes: 24 additions & 0 deletions zds/member/models.py
Expand Up @@ -360,6 +360,30 @@ def get_followed_topics(self):
return Topic.objects.filter(topicfollowed__user=self.user)\
.order_by('-last_message__pubdate')

@staticmethod
def has_read_permission(request):
return True

def has_object_read_permission(self, request):
return True

@staticmethod
def has_write_permission(request):
return True

def has_object_write_permission(self, request):
return self.has_object_update_permission(request) or request.user.has_perm("member.change_profile")

def has_object_update_permission(self, request):
return request.user.is_authenticated() and request.user == self.user

@staticmethod
def has_ban_permission(request):
return True

def has_object_ban_permission(self, request):
return request.user and request.user.has_perm("member.change_profile")


@receiver(models.signals.post_delete, sender=User)
def auto_delete_token_on_unregistering(sender, instance, **kwargs):
Expand Down
1 change: 1 addition & 0 deletions zds/settings.py
Expand Up @@ -166,6 +166,7 @@
'social.apps.django_app.default',
'rest_framework',
'rest_framework_swagger',
'dry_rest_permissions',
'corsheaders',
'oauth2_provider',
'captcha',
Expand Down