Skip to content

Commit

Permalink
Raised 404 exception when user wants to access someones profile whose…
Browse files Browse the repository at this point in the history
… privacy mode is set tp private
  • Loading branch information
amir-qayyum-khan committed Jun 24, 2016
1 parent 3d0cf3a commit 21f7135
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 72 deletions.
36 changes: 36 additions & 0 deletions profiles/permissions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"""
Permission classes for profiles
"""
from django.http import Http404
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import (
BasePermission,
SAFE_METHODS,
)

from profiles.models import Profile


class CanEditIfOwner(BasePermission):
"""
Expand All @@ -20,3 +24,35 @@ def has_object_permission(self, request, view, profile):
return True

return profile.user == request.user


class CanSeeIfNotPrivate(BasePermission):
"""
Only owner of a profile can view and edit their profile.
Verified micromaster users can view other profiles to limited view only if
account_privacy of profile is set to public public_to_mm
"""

def has_permission(self, request, view):
"""
Implementation of the permission class.
"""
profile = get_object_or_404(Profile, user__social_auth__uid=view.kwargs['user'])

if request.user == profile.user:
return True

# private profiles
if profile.account_privacy == Profile.PRIVATE:
raise Http404
elif profile.account_privacy == Profile.PUBLIC_TO_MM:
# anonymous user accessing profiles.
if request.user.is_anonymous():
raise Http404
# user who is not a micromaster verified user is accessing profiles.
elif not request.user.profile.verified_micromaster_user:
raise Http404
elif profile.account_privacy not in [Profile.PRIVATE, Profile.PUBLIC_TO_MM, Profile.PUBLIC]:
raise Http404

return True
145 changes: 141 additions & 4 deletions profiles/permissions_test.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
"""
Tests for profile permissions
"""

from mock import Mock
from django.http import Http404
from django.db.models.signals import post_save
from django.test import TestCase
from factory.django import mute_signals

from profiles.factories import ProfileFactory, UserFactory
from profiles.permissions import CanEditIfOwner
from backends.edxorg import EdxOrgOAuth2
from profiles.factories import (
ProfileFactory,
UserFactory,
)
from profiles.models import Profile
from profiles.permissions import (
CanEditIfOwner,
CanSeeIfNotPrivate,
)


# pylint: disable=no-self-use
Expand Down Expand Up @@ -47,6 +55,135 @@ def test_cant_edit_if_not_owner(self):
other_user = UserFactory.create()
for method in ('POST', 'PATCH', 'PUT'):
with mute_signals(post_save):
profile = ProfileFactory.create()
profile = ProfileFactory.create(account_privacy=Profile.PUBLIC_TO_MM)

request = Mock(method=method, user=other_user)
assert not perm.has_object_permission(request, None, profile)


# pylint: disable=no-self-use
class CanSeeIfNotPrivateTests(TestCase):
"""
Tests for CanSeeIfNotPrivate permissions
"""

def setUp(self):
super(CanSeeIfNotPrivateTests, self).setUp()
with mute_signals(post_save):
self.other_user = other_user = UserFactory.create()
username = "{}_edx".format(other_user.username)
social_auth = other_user.social_auth.create(
provider=EdxOrgOAuth2.name,
uid=username
)
self.other_user_id = social_auth.uid
ProfileFactory.create(user=other_user, verified_micromaster_user=False)

with mute_signals(post_save):
self.profile_user = profile_user = UserFactory.create()
username = "{}_edx".format(profile_user.username)
social_auth = profile_user.social_auth.create(
provider=EdxOrgOAuth2.name,
uid=username
)
self.profile_user_id = social_auth.uid

def test_cant_view_if_privacy_is_private(self):
"""
Users are not supposed to view private profiles.
"""
perm = CanSeeIfNotPrivate()

with mute_signals(post_save):
ProfileFactory.create(user=self.profile_user, account_privacy=Profile.PRIVATE)

request = Mock(user=self.other_user)
view = Mock(kwargs={'user': self.profile_user_id})

with self.assertRaises(Http404):
perm.has_permission(request, view)

def test_cant_view_if_anonymous_user(self):
"""
Anonymous are not supposed to view public_to_mm profiles.
"""
perm = CanSeeIfNotPrivate()
with mute_signals(post_save):
ProfileFactory.create(user=self.profile_user, account_privacy=Profile.PUBLIC_TO_MM)

request = Mock(user=Mock(is_anonymous=Mock(return_value=True)))
view = Mock(kwargs={'user': self.profile_user_id})

with self.assertRaises(Http404):
perm.has_permission(request, view)

def test_cant_view_if_non_verified_mm_user(self):
"""
Non verified micromaster users are not supposed to view public_to_mm profiles.
"""
perm = CanSeeIfNotPrivate()
with mute_signals(post_save):
ProfileFactory.create(user=self.profile_user, account_privacy=Profile.PUBLIC_TO_MM)

request = Mock(user=self.other_user)
view = Mock(kwargs={'user': self.profile_user_id})

with self.assertRaises(Http404):
perm.has_permission(request, view)

def test_cant_view_if_privacy_weird(self):
"""
Users can not open profiles with ambiguous account_privacy settings.
"""
perm = CanSeeIfNotPrivate()
with mute_signals(post_save):
ProfileFactory.create(user=self.profile_user, account_privacy='weird_setting')

request = Mock(user=self.other_user)
view = Mock(kwargs={'user': self.profile_user_id})

with self.assertRaises(Http404):
perm.has_permission(request, view)

def test_can_view_own_profile(self):
"""
Users are allowed to view their own profile.
"""
perm = CanSeeIfNotPrivate()
request = Mock(user=self.other_user)
view = Mock(kwargs={'user': self.other_user_id})

assert perm.has_permission(request, view) is True

def test_users_can_view_public_profile(self):
"""
Users are allowed to view public profile.
"""
perm = CanSeeIfNotPrivate()
with mute_signals(post_save):
ProfileFactory.create(user=self.profile_user, account_privacy=Profile.PUBLIC)

request = Mock(user=self.other_user)
view = Mock(kwargs={'user': self.profile_user_id})
assert perm.has_permission(request, view) is True

def test_can_view_if_verified_mm_user(self):
"""
Verified MM users are allowed to view public_to_mm profile.
"""
perm = CanSeeIfNotPrivate()
with mute_signals(post_save):
ProfileFactory.create(user=self.profile_user, account_privacy=Profile.PUBLIC_TO_MM)

with mute_signals(post_save):
verified_user = UserFactory.create()
username = "{}_edx".format(verified_user.username)
verified_user.social_auth.create(
provider=EdxOrgOAuth2.name,
uid=username
)
ProfileFactory.create(user=verified_user, verified_micromaster_user=True)

request = Mock(user=verified_user)
view = Mock(kwargs={'user': self.profile_user_id})
assert perm.has_permission(request, view) is True
19 changes: 0 additions & 19 deletions profiles/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,22 +227,3 @@ class Meta: # pylint: disable=missing-docstring
'edx_level_of_education',
'education'
)


class ProfilePrivateSerializer(ProfileBaseSerializer):
"""
Serializer for Profile objects, limited to fields that other users are
allowed to see if a profile is marked private.
"""

class Meta: # pylint: disable=missing-docstring
model = Profile
fields = (
'username',
'account_privacy',
'has_profile_image',
'profile_url_full',
'profile_url_large',
'profile_url_medium',
'profile_url_small',
)
16 changes: 0 additions & 16 deletions profiles/serializers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
EducationSerializer,
EmploymentSerializer,
ProfileLimitedSerializer,
ProfilePrivateSerializer,
ProfileSerializer,
)
from profiles.util import (
Expand Down Expand Up @@ -121,21 +120,6 @@ def test_limited(self): # pylint: disable=no-self-use
]
}

def test_private(self): # pylint: disable=no-self-use
"""
Test private serializer
"""
profile = self.create_profile()
assert ProfilePrivateSerializer().to_representation(profile) == {
'username': profile.user.social_auth.get(provider=EdxOrgOAuth2.name).uid,
'account_privacy': profile.account_privacy,
'has_profile_image': profile.has_profile_image,
'profile_url_full': format_gravatar_url(profile.user.email, GravatarImgSize.FULL),
'profile_url_large': format_gravatar_url(profile.user.email, GravatarImgSize.LARGE),
'profile_url_medium': format_gravatar_url(profile.user.email, GravatarImgSize.MEDIUM),
'profile_url_small': format_gravatar_url(profile.user.email, GravatarImgSize.SMALL),
}

def test_add_education(self):
"""
Test that we handle adding an Education correctly
Expand Down
29 changes: 10 additions & 19 deletions profiles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,40 @@
from profiles.serializers import (
ProfileSerializer,
ProfileLimitedSerializer,
ProfilePrivateSerializer,
)
from profiles.permissions import CanEditIfOwner
from profiles.permissions import (
CanEditIfOwner,
CanSeeIfNotPrivate,
)


class ProfileViewSet(RetrieveModelMixin, UpdateModelMixin, GenericViewSet):
"""API for the Program collection"""
# pylint: disable=too-many-return-statements

permission_classes = (CanEditIfOwner, )
permission_classes = (CanEditIfOwner, CanSeeIfNotPrivate, )
lookup_field = 'user__social_auth__uid'
lookup_url_kwarg = 'user'
queryset = Profile.objects.all()

# possible serializers
serializer_class_owner = ProfileSerializer
serializer_class_limited = ProfileLimitedSerializer
serializer_class_private = ProfilePrivateSerializer

def get_serializer_class(self):
"""
Different parts of a user profile are visible in different conditions
"""
profile = self.get_object()

# Case #1: Owner of the profile
# Owner of the profile
if self.request.user == profile.user:
return self.serializer_class_owner
# Case #2: Profile is private
elif profile.account_privacy == Profile.PRIVATE:
return self.serializer_class_private
# Case #3: Profile is public
# Profile is public
elif profile.account_privacy == Profile.PUBLIC:
return self.serializer_class_limited
# Case #4: Profile is public to mm verified users only
# Profile is public to mm verified users only
elif profile.account_privacy == Profile.PUBLIC_TO_MM:
# Case #3: anonymous user
if self.request.user.is_anonymous():
# the profile at this point is not public so the anonymous users can see only basic info
return self.serializer_class_private
# Case #4: the user is not a micromaster verified user
elif not self.request.user.profile.verified_micromaster_user:
return self.serializer_class_private
return self.serializer_class_limited
# Case #5: this should never happen, but just in case
return self.serializer_class_private
# this should never happen, but just in case
return self.serializer_class_limited
Loading

0 comments on commit 21f7135

Please sign in to comment.