Skip to content

Commit

Permalink
Merge branch 'master' of github.com:ProjetSigma/backend
Browse files Browse the repository at this point in the history
  • Loading branch information
tizot committed Feb 6, 2016
2 parents 30cd412 + 49b7377 commit f2e740a
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 93 deletions.
1 change: 0 additions & 1 deletion sigma/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from sigma_core.views.user import UserViewSet
from sigma_core.views.group import GroupViewSet
from sigma_core.views.school import SchoolViewSet
from sigma_core.views.group_user import GroupUserViewSet
from sigma_core.views.group_member import GroupMemberViewSet
from sigma_core.views.group_member_value import GroupMemberValueViewSet
from sigma_core.views.group_field import GroupFieldViewSet
Expand Down
2 changes: 2 additions & 0 deletions sigma_core/models/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ def has_object_read_permission(self, request):
"""
Public groups are visible by everybody. Private groups are only visible by members.
"""
# Handled in View directly with queryset override
return True
return not self.private or request.user.is_group_member(self)

@staticmethod
Expand Down
4 changes: 0 additions & 4 deletions sigma_core/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@


class UserManager(BaseUserManager):
# TODO: Determine whether 'memberships' fields needs to be retrieved every time or not...
# def get_queryset(self):
# return super(UserManager, self).get_queryset().prefetch_related('memberships', 'invited_to_groups')

def create_user(self, email, lastname, firstname, password=None):
"""
Creates and saves a User with the given email, lastname, firstname and password
Expand Down
18 changes: 15 additions & 3 deletions sigma_core/serializers/group_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
from sigma_core.models.user import User
from sigma_core.models.group import Group
from sigma_core.models.group_member import GroupMember
from sigma_core.serializers.group import BasicGroupSerializer


class GroupMemberSerializerMeta(object):
model = GroupMember
read_only_fields = ('perm_rank', )


class GroupMemberSerializer(serializers.ModelSerializer):
class Meta:
model = GroupMember
read_only_fields = ('perm_rank', )
class Meta(GroupMemberSerializerMeta):
pass

user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
group = serializers.PrimaryKeyRelatedField(queryset=Group.objects.all())
Expand All @@ -18,3 +23,10 @@ def create(self, validated_data):
mem.perm_rank = mem.group.default_member_rank
mem.save()
return mem


class GroupMemberSerializer_Group(GroupMemberSerializer):
class Meta(GroupMemberSerializerMeta):
read_only_fields = GroupMemberSerializerMeta.read_only_fields = ('group', )

group = BasicGroupSerializer()
23 changes: 16 additions & 7 deletions sigma_core/serializers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
from dry_rest_permissions.generics import DRYPermissionsField

from sigma_core.models.user import User
from sigma_core.serializers.group_member import GroupMemberSerializer_Group
from sigma_files.models import Image
from sigma_files.serializers import ImageSerializer


class BasicUserSerializerMeta(object):
model = User
exclude = ('is_staff', 'is_superuser', 'invited_to_groups', )
read_only_fields = ('last_login', 'is_active', ) # TODO: serialize invited_to_groups correctly
read_only_fields = ('last_login', 'is_active', 'photo') # TODO: serialize invited_to_groups correctly
extra_kwargs = {'password': {'write_only': True, 'required': False}}

class BasicUserSerializer(serializers.ModelSerializer):
Expand All @@ -18,7 +20,7 @@ class BasicUserSerializer(serializers.ModelSerializer):
class Meta(BasicUserSerializerMeta):
pass

photo = serializers.PrimaryKeyRelatedField(queryset=Image.objects.all(), allow_null=True, required=False)
photo = ImageSerializer(read_only=True)


from sigma_core.serializers.group_member import GroupMemberSerializer
Expand All @@ -37,18 +39,25 @@ class DetailedUserSerializer(BasicUserSerializer):
Serialize full data about an User.
"""
class Meta(BasicUserSerializerMeta):
exclude = ('is_staff', 'is_superuser', )
read_only_fields = BasicUserSerializerMeta.read_only_fields + ('invited_to_groups', )
pass

memberships = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
memberships = GroupMemberSerializer_Group(read_only=True, many=True)


class DetailedUserWithPermsSerializer(DetailedUserSerializer):
"""
Serialize full data about an User and add current user's permissions on the serialized User.
"""
class Meta(BasicUserSerializerMeta):
exclude = ('is_staff', 'is_superuser', )
read_only_fields = BasicUserSerializerMeta.read_only_fields + ('invited_to_groups', )
pass

permissions = DRYPermissionsField(read_only=True)


class MyUserDetailsWithPermsSerializer(DetailedUserWithPermsSerializer):
"""
Serialize full data about current User (with permissions).
"""
class Meta(BasicUserSerializerMeta):
exclude = ('is_staff', 'is_superuser', )
read_only_fields = BasicUserSerializerMeta.read_only_fields + ('invited_to_groups', )
2 changes: 1 addition & 1 deletion sigma_core/tests/test_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def test_get_group_forbidden(self):
# Non-member wants to see a private group
self.client.force_authenticate(user=self.users[0])
response = self.client.get(self.group_url % self.groups[1].id)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

def test_get_group_ok(self):
# Client wants to see a public group
Expand Down
58 changes: 47 additions & 11 deletions sigma_core/tests/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
from rest_framework import status
from rest_framework.test import APITestCase, force_authenticate

from sigma_core.tests.factories import UserFactory, AdminUserFactory
from sigma_core.tests.factories import UserFactory, AdminUserFactory, GroupMemberFactory, GroupFactory
from sigma_core.serializers.user import DetailedUserSerializer as UserSerializer

from sigma_core.models.group import Group
from sigma_core.models.group_member import GroupMember

class UserTests(APITestCase):
@classmethod
Expand All @@ -16,13 +17,21 @@ def setUpTestData(self):

self.user = UserFactory()
self.user2 = UserFactory()
self.user3 = UserFactory()
self.group23 = GroupFactory()
GroupMemberFactory(group=self.group23, user=self.user2, perm_rank=0)
GroupMemberFactory(group=self.group23, user=self.user3, perm_rank=1)
self.group23_bis = GroupFactory()
GroupMemberFactory(group=self.group23_bis, user=self.user2, perm_rank=1)
GroupMemberFactory(group=self.group23_bis, user=self.user3, perm_rank=1)
self.admin_user = AdminUserFactory()

serializer = UserSerializer(self.user)
self.user_data = serializer.data
self.user_url = '/user/%d/' % self.user.id

self.users_list = [self.user, self.user2, self.admin_user]
self.users_list_for_user3 = [self.user2, self.user3]

self.new_user_data = {'lastname': 'Doe', 'firstname': 'John', 'email': 'john.doe@newschool.edu', 'password': 'password'}

Expand All @@ -40,22 +49,35 @@ def test_get_users_list_unauthed(self):

def test_get_users_list_ok(self):
# Client has permissions
self.client.force_authenticate(user=self.user)
self.client.force_authenticate(user=self.user3)
response = self.client.get('/user/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), len(self.users_list))
self.assertEqual(len(response.data), len(self.users_list_for_user3))

#### Get requests
def test_get_user_unauthed(self):
# Client is not authenticated
response = self.client.get(self.user_url)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

# def test_get_user_forbidden(self):
# # Client authenticated but has no permission
# self.client.force_authenticate(user=self.user2)
# response = self.client.get(self.user_url)
# self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_get_user_forbidden_no_common_group(self):
# Client authenticated but has no group in common
self.client.force_authenticate(user=self.user)
response = self.client.get("/user/%d/" % self.user3.id)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

def test_get_user_forbidden_common_group_not_accepted(self):
# Client authenticated, group in common, but not accepted in this Group
user4 = UserFactory()
GroupMemberFactory(group=self.group23, user=user4, perm_rank=0)
self.client.force_authenticate(user=user4)
response = self.client.get("/user/%d/" % self.user2.id)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

def test_get_user_ok_same_group(self):
self.client.force_authenticate(user=self.user3)
response = self.client.get("/user/%d/" % self.user2.id)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_get_user_ok(self):
# Client has permissions
Expand All @@ -65,6 +87,20 @@ def test_get_user_ok(self):
response.data.pop('permissions', None) # Workaround because DRY rest permissions needs a request
self.assertEqual(response.data, self.user_data)

def test_get_user_memberships_all_visible(self):
# User3 is in both groups
self.client.force_authenticate(user=self.user3)
response = self.client.get('/user/%d/' % self.user2.id)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['memberships']), 2)

def test_get_user_memberships_only_one_visible(self):
# User2 is in both groups, but not accepted in the first group
self.client.force_authenticate(user=self.user2)
response = self.client.get('/user/%d/' % self.user3.id)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['memberships']), 1)

#### "Get my data" requests
def test_get_my_data_unauthed(self):
# Client is not authenticated
Expand Down Expand Up @@ -104,7 +140,7 @@ def test_edit_email_wrong_permission(self):
user_data = UserSerializer(self.user2).data
user_data['email'] = "pi@random.org"
response = self.client.put("/user/%d/" % self.user2.id, user_data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

def test_edit_is_superuser_no_permission(self):
# Client can't set himself as administrator !
Expand Down Expand Up @@ -140,7 +176,7 @@ def test_edit_profile_wrong_permission(self):
user_data = UserSerializer(self.user2).data
user_data['phone'] = "0123456789"
response = self.client.put("/user/%d/" % self.user2.id, user_data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

def test_edit_profile_ok(self):
# Client wants to change his phone number
Expand Down
4 changes: 3 additions & 1 deletion sigma_core/views/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@


class GroupFilterBackend(DRYPermissionFiltersBase):
def filter_list_queryset(self, request, queryset, view):
def filter_queryset(self, request, queryset, view):
"""
Limits all list requests to only be seen by the members or public groups.
"""
if request.user.is_sigma_admin():
return queryset
return queryset.prefetch_related('memberships__user').filter(Q(private=False) | Q(memberships__user=request.user)).distinct()


Expand Down
53 changes: 0 additions & 53 deletions sigma_core/views/group_user.py

This file was deleted.

41 changes: 29 additions & 12 deletions sigma_core/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
import string

from django.core.mail import send_mail
from django.db.models import Q, Prefetch
from django.http import Http404
from django.views.decorators.csrf import csrf_exempt

from rest_framework import viewsets, decorators, status, parsers
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from dry_rest_permissions.generics import DRYPermissions
from dry_rest_permissions.generics import DRYPermissions, DRYPermissionFiltersBase

from sigma_core.models.user import User
from sigma_core.serializers.user import BasicUserWithPermsSerializer, DetailedUserWithPermsSerializer
from sigma_core.models.group_member import GroupMember
from sigma_core.serializers.user import BasicUserWithPermsSerializer, DetailedUserWithPermsSerializer, MyUserDetailsWithPermsSerializer


reset_mail = {
Expand All @@ -27,26 +29,38 @@
"""
}

class VisibleUsersFilterBackend(DRYPermissionFiltersBase):
def filter_queryset(self, request, queryset, view):
""" break sigma_core/views/user.py:41
Never display information for Users who have no group in common
"""
if request.user.is_sigma_admin():
return queryset
from sigma_core.models.group_member import GroupMember
# @sqlperf: Maybe it is more efficient with only one Query?
my_groups = GroupMember.objects.filter(Q(user=request.user) & Q(perm_rank__gte=1)).values_list('group', flat=True)
visible_users_ids = GroupMember.objects.filter(group__in=my_groups).values('user')
return queryset.filter(Q(pk__in=visible_users_ids) | Q(pk=request.user.id))

# TODO: use DetailSerializerMixin
class UserViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, DRYPermissions, ]
queryset = User.objects.all()
serializer_class = BasicUserWithPermsSerializer # by default, basic data and permissions
filter_backends = (VisibleUsersFilterBackend, )

def retrieve(self, request, pk=None):
"""
Retrieve an User according to its id (pk).
---
response_serializer: DetailedUserWithPermsSerializer
"""
try:
user = User.objects.get(pk=pk)
except User.DoesNotExist:
raise Http404()

# Use DetailedUserWithPermsSerializer to have the groups whom user belongs to
serializer = DetailedUserWithPermsSerializer(user, context={'request': request})
return Response(serializer.data, status=status.HTTP_200_OK)
my_groups = GroupMember.objects.filter(Q(user=request.user) & Q(perm_rank__gte=1)).values_list('group', flat=True)
self.queryset = self.queryset.select_related('photo').prefetch_related(
Prefetch('memberships', queryset=GroupMember.objects.filter(group__in=my_groups).select_related('group'))
)
self.serializer_class = DetailedUserWithPermsSerializer
return super().retrieve(request, pk)

def update(self, request, pk=None):
try:
Expand All @@ -71,8 +85,11 @@ def me(self, request):
return Response(status=status.HTTP_401_UNAUTHORIZED)
else:
# Use DetailedUserWithPermsSerializer to have the groups whom user belongs to
serializer = DetailedUserWithPermsSerializer(request.user, context={'request': request})
return Response(serializer.data)
user = User.objects.all().select_related('photo').prefetch_related(
Prefetch('memberships', queryset=GroupMember.objects.all().select_related('group'))
).get(pk=request.user.id)
s = MyUserDetailsWithPermsSerializer(user, context={'request': request})
return Response(s.data, status=status.HTTP_200_OK)

@decorators.list_route(methods=['put'])
def change_password(self, request):
Expand Down

0 comments on commit f2e740a

Please sign in to comment.