Skip to content

Commit

Permalink
Merge pull request #853 from openedx/ENT-9072/update-permissions-for-PAs
Browse files Browse the repository at this point in the history
feat: grant provisioning-admins access to few views
  • Loading branch information
hamzawaleed01 authored Jun 26, 2024
2 parents 07d4a04 + 10f5bcb commit 028c077
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 2 deletions.
88 changes: 88 additions & 0 deletions enterprise_catalog/apps/api/v1/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import ddt
import pytz
from django.conf import settings
from django.contrib.auth.models import Group
from django.db import IntegrityError
from django.utils.http import urlencode
from django.utils.text import slugify
Expand All @@ -31,6 +32,7 @@
EXEC_ED_2U_ENTITLEMENT_MODE,
LEARNER_PATHWAY,
PROGRAM,
PROVISIONING_ADMINS_GROUP,
)
from enterprise_catalog.apps.catalog.models import (
CatalogQuery,
Expand Down Expand Up @@ -177,6 +179,7 @@ def setUp(self):
'publish_audit_enrollment_urls': True,
'content_filter': {'content_type': 'course'},
}
self.allowed_group = Group.objects.create(name=PROVISIONING_ADMINS_GROUP)

def _assert_correct_new_catalog_data(self, catalog_uuid):
"""
Expand Down Expand Up @@ -250,6 +253,22 @@ def test_detail(self, is_implicit_check):
self.assertEqual(data['title'], self.enterprise_catalog.title)
self.assertEqual(uuid.UUID(data['enterprise_customer']), self.enterprise_catalog.enterprise_uuid)

def test_detail_provisioning_admin(self):
"""
Verify the viewset returns the details if requesting user is a PA
"""
self.set_up_staff_user()
self.remove_role_assignments()
self.set_up_invalid_jwt_role()
self.user.groups.add(self.allowed_group)
url = reverse('api:v1:enterprise-catalog-detail', kwargs={'uuid': self.enterprise_catalog.uuid})
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.data
self.assertEqual(uuid.UUID(data['uuid']), self.enterprise_catalog.uuid)
self.assertEqual(data['title'], self.enterprise_catalog.title)
self.assertEqual(uuid.UUID(data['enterprise_customer']), self.enterprise_catalog.enterprise_uuid)

def test_detail_unauthorized_non_catalog_admin(self):
"""
Verify the viewset rejects users that are not catalog admins for the detail route
Expand Down Expand Up @@ -297,6 +316,29 @@ def test_patch(self, is_implicit_check):
self.enterprise_catalog.publish_audit_enrollment_urls,
)

def test_patch_provisioning_admins(self):
"""
Verify the viewset handles patching an enterprise catalog
"""
self.set_up_staff_user()
self.remove_role_assignments()
self.set_up_invalid_jwt_role()
self.user.groups.add(self.allowed_group)
url = reverse('api:v1:enterprise-catalog-detail', kwargs={'uuid': self.enterprise_catalog.uuid})
patch_data = {'title': 'Patch title'}
response = self.client.patch(url, patch_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Verify that only the data we specifically patched changed
self.assertEqual(response.data['title'], patch_data['title'])
patched_catalog = EnterpriseCatalog.objects.get(uuid=self.enterprise_catalog.uuid)
self.assertEqual(patched_catalog.catalog_query, self.enterprise_catalog.catalog_query)
self.assertEqual(patched_catalog.enterprise_uuid, self.enterprise_catalog.enterprise_uuid)
self.assertEqual(patched_catalog.enabled_course_modes, self.enterprise_catalog.enabled_course_modes)
self.assertEqual(
patched_catalog.publish_audit_enrollment_urls,
self.enterprise_catalog.publish_audit_enrollment_urls,
)

def test_patch_unauthorized_non_catalog_admin(self):
"""
Verify the viewset rejects patch for users that are not catalog admins
Expand Down Expand Up @@ -336,6 +378,19 @@ def test_put(self, is_implicit_check):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self._assert_correct_new_catalog_data(self.enterprise_catalog.uuid) # The UUID should not have changed

def test_put_provisioning_admins(self):
"""
Verify the viewset allows access to PAs
"""
self.set_up_staff_user()
self.remove_role_assignments()
self.set_up_invalid_jwt_role()
self.user.groups.add(self.allowed_group)
url = reverse('api:v1:enterprise-catalog-detail', kwargs={'uuid': self.enterprise_catalog.uuid})
response = self.client.put(url, self.new_catalog_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self._assert_correct_new_catalog_data(self.enterprise_catalog.uuid) # The UUID should not have changed

def test_put_unauthorized_non_catalog_admin(self):
"""
Verify the viewset rejects put for users that are not catalog admins
Expand Down Expand Up @@ -413,6 +468,19 @@ def test_post(self, is_implicit_check):
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self._assert_correct_new_catalog_data(self.new_catalog_uuid)

def test_post_provisioning_admins(self):
"""
Verify the viewset handles creating an enterprise catalog
"""
self.set_up_staff_user()
self.remove_role_assignments()
self.set_up_invalid_jwt_role()
self.user.groups.add(self.allowed_group)
url = reverse('api:v1:enterprise-catalog-list')
response = self.client.post(url, self.new_catalog_data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self._assert_correct_new_catalog_data(self.new_catalog_uuid)

def test_post_integrity_error(self):
"""
Verify the viewset raises error when creating a duplicate enterprise catalog
Expand Down Expand Up @@ -464,6 +532,7 @@ def setUp(self):
super().setUp()
self.set_up_staff_user()
self.enterprise_catalog = EnterpriseCatalogFactory(enterprise_uuid=self.enterprise_uuid)
self.allowed_group = Group.objects.create(name=PROVISIONING_ADMINS_GROUP)

def test_list_for_superusers(self):
"""
Expand All @@ -479,6 +548,25 @@ def test_list_for_superusers(self):
self.assertEqual(uuid.UUID(results[0]['uuid']), self.enterprise_catalog.uuid)
self.assertEqual(uuid.UUID(results[1]['uuid']), second_enterprise_catalog.uuid)

def test_list_for_provisioning_admins(self):
"""
Verify the viewset returns a list of all enterprise catalogs for provisioning admins
"""
self.set_up_staff_user()
self.remove_role_assignments()
self.set_up_invalid_jwt_role()
self.user.groups.add(self.allowed_group)
url = reverse('api:v1:enterprise-catalog-list')
second_enterprise_catalog = EnterpriseCatalogFactory()
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 2)
results = response.data['results']
self.assertEqual(
uuid.UUID(results[0]['uuid']), self.enterprise_catalog.uuid)
self.assertEqual(
uuid.UUID(results[1]['uuid']), second_enterprise_catalog.uuid)

def test_empty_list_for_non_catalog_admin(self):
"""
Verify the viewset returns an empty list for users that are staff but not catalog admins.
Expand Down
13 changes: 11 additions & 2 deletions enterprise_catalog/apps/api/v1/views/enterprise_catalog_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
EnterpriseCatalogSerializer,
)
from enterprise_catalog.apps.api.v1.views.base import BaseViewSet
from enterprise_catalog.apps.catalog.constants import PROVISIONING_ADMINS_GROUP
from enterprise_catalog.apps.catalog.models import EnterpriseCatalog
from enterprise_catalog.apps.catalog.rules import (
enterprises_with_admin_access,
Expand Down Expand Up @@ -64,6 +65,11 @@ def check_permissions(self, request):
If `get_permission_object` is implemented, it will be called and should return the object
for which the `rules` predicate checks against.
"""
# Grant provisioning-admins access to few actions
if self.request_action in ('create', 'partial_update', 'update', 'retrieve', 'list') and \
request.user.groups.filter(name=PROVISIONING_ADMINS_GROUP).exists():
return

if self.request_action == 'list':
# Super-users and staff won't get Forbidden responses,
# but depending on their assigned roles, staff may
Expand All @@ -78,16 +84,19 @@ def check_permissions(self, request):
def get_queryset(self):
"""
Returns the queryset corresponding to all catalogs the requesting user has access to.
Also allows provisioning admins to access all catalogs.
"""
all_catalogs = EnterpriseCatalog.objects.all().order_by('created')
enterprise_customer = self.request.GET.get('enterprise_customer', False)
is_provisioning_admin = self.request.user.groups.filter(
name=PROVISIONING_ADMINS_GROUP).exists()
if enterprise_customer:
all_catalogs = all_catalogs.filter(enterprise_uuid=enterprise_customer)

if self.request_action == 'list':
if not self.admin_accessible_enterprises:
if not self.admin_accessible_enterprises and not is_provisioning_admin:
return EnterpriseCatalog.objects.none()
if has_access_to_all_enterprises(self.admin_accessible_enterprises):
if has_access_to_all_enterprises(self.admin_accessible_enterprises) or is_provisioning_admin:
return all_catalogs
return all_catalogs.filter(enterprise_uuid__in=self.admin_accessible_enterprises)
return all_catalogs
1 change: 1 addition & 0 deletions enterprise_catalog/apps/catalog/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
}

FORCE_INCLUSION_METADATA_TAG_KEY = 'enterprise_force_included'
PROVISIONING_ADMINS_GROUP = "provisioning-admins-group"


def json_serialized_course_modes():
Expand Down

0 comments on commit 028c077

Please sign in to comment.