Skip to content
83 changes: 83 additions & 0 deletions cms/djangoapps/contentstore/tests/test_course_create_rerun.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from opaque_keys.edx.keys import CourseKey

from contentstore.tests.utils import AjaxEnabledTestClient, parse_json
from student.models import CourseAccessRole
from student.roles import CourseInstructorRole, CourseStaffRole
from student.tests.factories import UserFactory
from util.organizations_helpers import add_organization, get_course_organizations
Expand All @@ -34,6 +35,9 @@ def setUp(self):
"""
super(TestCourseListing, self).setUp()
# create and log in a staff user.
self.admin_user = UserFactory(is_staff=True)
self.admin_client = AjaxEnabledTestClient()
self.admin_client.login(username=self.admin_user.username, password='test')
# create and log in a non-staff user
self.user = UserFactory()
self.factory = RequestFactory()
Expand Down Expand Up @@ -64,6 +68,7 @@ def tearDown(self):
Reverse the setup
"""
self.client.logout()
self.admin_client.logout()
ModuleStoreTestCase.tearDown(self)

@patch.dict('django.conf.settings.FEATURES', {'ORGANIZATIONS_APP': True})
Expand Down Expand Up @@ -176,3 +181,81 @@ def test_course_creation_with_org_in_system(self, store):
course_orgs = get_course_organizations(new_course_key)
self.assertEqual(len(course_orgs), 1)
self.assertEqual(course_orgs[0]['short_name'], 'orgX')

@patch.dict('django.conf.settings.FEATURES', {'RESTRICT_COURSE_CREATION_TO_ORG_ROLES': True})
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_course_creation_when_user_not_in_org(self, store):
"""
Tests course creation with restriction and user not regitered in CourseAcessRole.
"""
with modulestore().default_store(store):
response = self.client.ajax_post(self.course_create_rerun_url, {
'org': 'TestorgX',
'number': 'CS101',
'display_name': 'Course with web certs enabled',
'run': '2021_T1'
})
self.assertEqual(response.status_code, 400)
data = parse_json(response)
self.assertEqual(
data["error"],
'User does not have the permission to create courses in this organization'
)

@patch.dict('django.conf.settings.FEATURES', {'RESTRICT_COURSE_CREATION_TO_ORG_ROLES': True})
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_course_creation_when_user_in_org(self, store):
"""
Tests course creation with restriction and user registered as staff.
"""
staff_role = 'staff'
CourseAccessRole.objects.create(
org='TestorgX', role=staff_role, user=self.user
)
with modulestore().default_store(store):
response = self.client.ajax_post(self.course_create_rerun_url, {
'org': 'TestorgX',
'number': 'CS101',
'display_name': 'Course with web certs enabled',
'run': '2021_T1'
})
self.assertEqual(response.status_code, 200)

@patch.dict('django.conf.settings.FEATURES', {'RESTRICT_COURSE_CREATION_TO_ORG_ROLES': True})
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_course_creation_when_user_in_org_with_non_access_role(self, store):
"""
Tests course creation with restriction and user registered as role who doesn't have the acess.
"""
staff_role = 'finance_admin'
CourseAccessRole.objects.create(
org='Stark', role=staff_role, user=self.user
)
with modulestore().default_store(store):
response = self.client.ajax_post(self.course_create_rerun_url, {
'org': 'Stark',
'number': 'AV101',
'display_name': 'Build Iron Man Suit',
'run': '2021_T1'
})
self.assertEqual(response.status_code, 400)
data = parse_json(response)
self.assertEqual(
data["error"],
'User does not have the permission to create courses in this organization'
)

@patch.dict('django.conf.settings.FEATURES', {'RESTRICT_COURSE_CREATION_TO_ORG_ROLES': True})
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_course_creation_when_user_is_global_staff(self, store):
"""
Tests course creation with restriction and user is global staff.
"""
with modulestore().default_store(store):
response = self.admin_client.ajax_post(self.course_create_rerun_url, {
'org': 'Oscorp',
'number': 'SP101',
'display_name': 'Making better web',
'run': '2021_T1'
})
self.assertEqual(response.status_code, 200)
72 changes: 72 additions & 0 deletions cms/djangoapps/contentstore/tests/test_libraries.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
OrgLibraryUserRole,
OrgStaffRole
)
from student.models import CourseAccessRole
from student.tests.factories import UserFactory
from xblock_django.user_service import DjangoXBlockUserService
from xmodule.modulestore import ModuleStoreEnum
Expand Down Expand Up @@ -813,6 +814,77 @@ def _get_settings_html():
self.assertNotIn('admin_lib_2', non_staff_settings_html)


@patch.dict('django.conf.settings.FEATURES', {'RESTRICT_COURSE_CREATION_TO_ORG_ROLES': True})
def test_library_creation_when_user_is_global_staff(self):
"""
Tests course creation with restriction and user is global staff.
"""
self._login_as_staff_user()
response = self.client.ajax_post(LIBRARY_REST_URL, {
'org': 'Oscorp',
'library': 'CentralLibrary',
'display_name': 'Making better web',
})
self.assertEqual(response.status_code, 200)

@patch.dict('django.conf.settings.FEATURES', {'RESTRICT_COURSE_CREATION_TO_ORG_ROLES': True})
def test_library_creation_with_normaL_user_with_no_role(self):
"""
Tests course creation with restriction and user is not a global staff.
"""
self._login_as_non_staff_user()
response = self.client.ajax_post(LIBRARY_REST_URL, {
'org': 'Stark',
'library': 'AvengerLibrary',
'display_name': 'Alien Science',
})
self.assertEqual(response.status_code, 400)
data = parse_json(response)
self.assertEqual(
data["ErrMsg"],
"User does not have the permission to create library in this organization"
)

@patch.dict('django.conf.settings.FEATURES', {'RESTRICT_COURSE_CREATION_TO_ORG_ROLES': True})
def test_library_creation_with_normaL_user_with_non_acess_role(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
def test_library_creation_with_normaL_user_with_non_acess_role(self):
def test_library_creation_with_normaL_user_with_non_access_role(self):

"""
Tests course creation with restriction and user doesn't have access role for org.
"""
staff_role = "finance_admin"
self._login_as_non_staff_user()
CourseAccessRole.objects.create(
org='Stark', role=staff_role, user=self.non_staff_user
)
response = self.client.ajax_post(LIBRARY_REST_URL, {
'org': 'Stark',
'library': 'AvengerLibrary',
'display_name': 'Alien Science',
})
self.assertEqual(response.status_code, 400)
data = parse_json(response)
self.assertEqual(
data["ErrMsg"],
"User does not have the permission to create library in this organization"
)

@patch.dict('django.conf.settings.FEATURES', {'RESTRICT_COURSE_CREATION_TO_ORG_ROLES': True})
def test_library_creation_with_normaL_user_with_role(self):
"""
Tests course creation with restriction and user has role access.
"""
staff_role = "instructor"
self._login_as_non_staff_user()
CourseAccessRole.objects.create(
org='Stark', role=staff_role, user=self.non_staff_user
)
response = self.client.ajax_post(LIBRARY_REST_URL, {
'org': 'Stark',
'library': 'AvengerLibrary',
'display_name': 'Alien Science',
})
self.assertEqual(response.status_code, 200)


@ddt.ddt
@override_settings(SEARCH_ENGINE=None)
class TestOverrides(LibraryTestCase):
Expand Down
10 changes: 10 additions & 0 deletions cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,16 @@ def _create_or_rerun_course(request):
status=400
)

# Allow user to create the course only if they belong to the organisation
# This flag doesn't apply to Global Staff and Superusers
if settings.FEATURES.get('RESTRICT_COURSE_CREATION_TO_ORG_ROLES', False):
has_org_permission = has_studio_write_access(request.user, None, org)
if not has_org_permission:
return JsonResponse(
Comment thread
lgp171188 marked this conversation as resolved.
{'error': _('User does not have the permission to create courses in this organization')},
status=400
)

fields = {'start': start}
if display_name is not None:
fields['display_name'] = display_name
Expand Down
9 changes: 9 additions & 0 deletions cms/djangoapps/contentstore/views/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ def _create_library(request):
library = request.json.get('number', None)
if library is None:
library = request.json['library']
# Allow user to create libraries only if they belong to the organization
# This flag doesn't apply to Global Staff and Superusers
if settings.FEATURES.get('RESTRICT_COURSE_CREATION_TO_ORG_ROLES', False):
has_org_permission = has_studio_write_access(request.user, None, org)
if not has_org_permission:
log.exception("User does not have the permission to create library in this organization")

@lgp171188 lgp171188 Feb 22, 2021 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@farhaanbukhsh, nit: create a library or create libraries. Same below. It is also a good idea to log the user id, org and library/course name. What do you think?

return JsonResponseBadRequest({
"ErrMsg": _(u"User does not have the permission to create library in this organization")
Comment thread
lgp171188 marked this conversation as resolved.
})
store = modulestore()
with store.default_store(ModuleStoreEnum.Type.split):
new_lib = store.create_library(
Expand Down
15 changes: 15 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,21 @@
# .. toggle_tickets: https://openedx.atlassian.net/browse/DEPR-58
# .. toggle_status: supported
'DEPRECATE_OLD_COURSE_KEYS_IN_STUDIO': True,

# .. toggle_name: RESTRICT_COURSE_CREATION_TO_ORG_ROLES
# .. toggle_implementation: DjangoSetting
# .. toggle_default: False
# .. toggle_description: Restricts users from creating courses/libraries in organisations
# which they don't belong to. This flag doesn't apply to Global Staff and Superusers.
# To enable, set to True.
# To disable, set to False.
# .. toggle_category: n/a
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2021-06-23
# .. toggle_expiration_date: None
# .. toggle_status: supported
'RESTRICT_COURSE_CREATION_TO_ORG_ROLES': False

}

ENABLE_JASMINE = False
Expand Down
5 changes: 3 additions & 2 deletions common/djangoapps/student/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def get_user_permissions(user, course_key, org=None):
return STUDIO_NO_PERMISSIONS


def has_studio_write_access(user, course_key):
def has_studio_write_access(user, course_key, org=None):
"""
Return True if user has studio write access to the given course.
Note that the CMS permissions model is with respect to courses.
Expand All @@ -112,8 +112,9 @@ def has_studio_write_access(user, course_key):

:param user:
:param course_key: a CourseKey
:param org: name of organisation
"""
return bool(STUDIO_EDIT_CONTENT & get_user_permissions(user, course_key))
return bool(STUDIO_EDIT_CONTENT & get_user_permissions(user, course_key, org))


def has_course_author_access(user, course_key):
Expand Down