Commit 098dd992 by Clinton Blackburn

Added username filtering to catalog list endpoint

ECOM-4087
parent 6ea078e8
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext as _
from dry_rest_permissions.generics import DRYPermissionFiltersBase from dry_rest_permissions.generics import DRYPermissionFiltersBase
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from rest_framework.exceptions import PermissionDenied, NotFound
User = get_user_model()
class PermissionsFilter(DRYPermissionFiltersBase): class PermissionsFilter(DRYPermissionFiltersBase):
def filter_list_queryset(self, request, queryset, view): def filter_list_queryset(self, request, queryset, view):
""" Filters the list queryset, returning only the objects accessible by the user. """ """ Filters the list queryset, returning only the objects accessible by the user.
If a username parameter is passed on the querystring, the filter will will return objects accessible by
the user corresponding to the given username. NOTE: This functionality is only accessible to staff users.
Raises:
PermissionDenied -- If a username querystring parameter is specified, but the user is not a staff user.
Http404 -- If no `User` corresponding to the given username exists.
Returns:
`QuerySet`
"""
perm = queryset.model.get_permission('view') perm = queryset.model.get_permission('view')
return get_objects_for_user(request.user, perm) user = request.user
username = request.query_params.get('username', None)
if username:
if request.user.is_staff:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise NotFound(_('No user with the username [{username}] exists.').format(username=username))
else:
raise PermissionDenied(
_('Only staff users are permitted to filter by username. Remove the username parameter.')
)
return get_objects_for_user(user, perm)
...@@ -20,6 +20,7 @@ from course_discovery.apps.course_metadata.tests.factories import CourseRunFacto ...@@ -20,6 +20,7 @@ from course_discovery.apps.course_metadata.tests.factories import CourseRunFacto
@ddt.ddt @ddt.ddt
class CatalogViewSetTests(ElasticsearchTestMixin, SerializationMixin, OAuth2Mixin, APITestCase): class CatalogViewSetTests(ElasticsearchTestMixin, SerializationMixin, OAuth2Mixin, APITestCase):
""" Tests for the catalog resource. """ """ Tests for the catalog resource. """
catalog_list_url = reverse('api:v1:catalog-list')
def setUp(self): def setUp(self):
super(CatalogViewSetTests, self).setUp() super(CatalogViewSetTests, self).setUp()
...@@ -39,7 +40,7 @@ class CatalogViewSetTests(ElasticsearchTestMixin, SerializationMixin, OAuth2Mixi ...@@ -39,7 +40,7 @@ class CatalogViewSetTests(ElasticsearchTestMixin, SerializationMixin, OAuth2Mixi
'query': query 'query': query
} }
response = self.client.post(reverse('api:v1:catalog-list'), data, format='json', **headers) response = self.client.post(self.catalog_list_url, data, format='json', **headers)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
catalog = Catalog.objects.latest() catalog = Catalog.objects.latest()
...@@ -47,18 +48,19 @@ class CatalogViewSetTests(ElasticsearchTestMixin, SerializationMixin, OAuth2Mixi ...@@ -47,18 +48,19 @@ class CatalogViewSetTests(ElasticsearchTestMixin, SerializationMixin, OAuth2Mixi
self.assertEqual(catalog.name, name) self.assertEqual(catalog.name, name)
self.assertEqual(catalog.query, query) self.assertEqual(catalog.query, query)
def grant_catalog_permission_to_user(self, user, action): def grant_catalog_permission_to_user(self, user, action, catalog=None):
""" Grant the user access to view `self.catalog`. """ """ Grant the user access to view `self.catalog`. """
catalog = catalog or self.catalog
perm = '{action}_catalog'.format(action=action) perm = '{action}_catalog'.format(action=action)
user.add_obj_perm(perm, self.catalog) user.add_obj_perm(perm, catalog)
self.assertTrue(user.has_perm('catalogs.' + perm, self.catalog)) self.assertTrue(user.has_perm('catalogs.' + perm, catalog))
def test_create_without_authentication(self): def test_create_without_authentication(self):
""" Verify authentication is required when creating, updating, or deleting a catalog. """ """ Verify authentication is required when creating, updating, or deleting a catalog. """
self.client.logout() self.client.logout()
Catalog.objects.all().delete() Catalog.objects.all().delete()
response = self.client.post(reverse('api:v1:catalog-list'), {}, format='json') response = self.client.post(self.catalog_list_url, {}, format='json')
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertEqual(Catalog.objects.count(), 0) self.assertEqual(Catalog.objects.count(), 0)
...@@ -120,9 +122,7 @@ class CatalogViewSetTests(ElasticsearchTestMixin, SerializationMixin, OAuth2Mixi ...@@ -120,9 +122,7 @@ class CatalogViewSetTests(ElasticsearchTestMixin, SerializationMixin, OAuth2Mixi
def test_list(self): def test_list(self):
""" Verify the endpoint returns a list of all catalogs. """ """ Verify the endpoint returns a list of all catalogs. """
url = reverse('api:v1:catalog-list') response = self.client.get(self.catalog_list_url)
response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertListEqual(response.data['results'], self.serialize_catalog(Catalog.objects.all(), many=True)) self.assertListEqual(response.data['results'], self.serialize_catalog(Catalog.objects.all(), many=True))
...@@ -189,16 +189,15 @@ class CatalogViewSetTests(ElasticsearchTestMixin, SerializationMixin, OAuth2Mixi ...@@ -189,16 +189,15 @@ class CatalogViewSetTests(ElasticsearchTestMixin, SerializationMixin, OAuth2Mixi
""" Verify only catalogs accessible to the user are returned in the list view. """ """ Verify only catalogs accessible to the user are returned in the list view. """
user = UserFactory(is_staff=False, is_superuser=False) user = UserFactory(is_staff=False, is_superuser=False)
self.client.force_authenticate(user) self.client.force_authenticate(user)
url = reverse('api:v1:catalog-list')
# An user with no permissions should not see any catalogs # An user with no permissions should not see any catalogs
response = self.client.get(url) response = self.client.get(self.catalog_list_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertListEqual(response.data['results'], []) self.assertListEqual(response.data['results'], [])
# The client should be able to see permissions for which it has access # The client should be able to see permissions for which it has access
self.grant_catalog_permission_to_user(user, 'view') self.grant_catalog_permission_to_user(user, 'view')
response = self.client.get(url) response = self.client.get(self.catalog_list_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertListEqual(response.data['results'], self.serialize_catalog([self.catalog], many=True)) self.assertListEqual(response.data['results'], self.serialize_catalog([self.catalog], many=True))
...@@ -222,3 +221,39 @@ class CatalogViewSetTests(ElasticsearchTestMixin, SerializationMixin, OAuth2Mixi ...@@ -222,3 +221,39 @@ class CatalogViewSetTests(ElasticsearchTestMixin, SerializationMixin, OAuth2Mixi
self.grant_catalog_permission_to_user(user, 'delete') self.grant_catalog_permission_to_user(user, 'delete')
response = self.client.delete(url) response = self.client.delete(url)
self.assertEqual(response.status_code, 204) self.assertEqual(response.status_code, 204)
def test_username_filter_as_non_staff_user(self):
""" Verify HTTP 403 is returned when a non-staff user attempts to filter the Catalog list by username. """
user = UserFactory(is_staff=False, is_superuser=False)
self.client.force_authenticate(user)
response = self.client.get(self.catalog_list_url + '?username=jack')
self.assertEqual(response.status_code, 403)
expected = {'detail': 'Only staff users are permitted to filter by username. Remove the username parameter.'}
self.assertDictEqual(response.data, expected)
def test_username_filter_as_staff_user(self):
""" Verify a list of Catalogs accessible by the given user is returned when filtering by username as a
staff user. """
user = UserFactory(is_staff=False, is_superuser=False)
catalog = CatalogFactory()
path = '{root}?username={username}'.format(root=self.catalog_list_url, username=user.username)
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertListEqual(response.data['results'], [])
self.grant_catalog_permission_to_user(user, 'view', catalog)
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertListEqual(response.data['results'], self.serialize_catalog([catalog], many=True))
def test_username_filter_as_staff_user_with_invalid_username(self):
""" Verify HTTP 404 is returned if the given username does not correspond to an actual user. """
username = 'jack'
path = '{root}?username={username}'.format(root=self.catalog_list_url, username=username)
response = self.client.get(path)
self.assertEqual(response.status_code, 404)
expected = {'detail': 'No user with the username [{username}] exists.'.format(username=username)}
self.assertDictEqual(response.data, expected)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment