Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -14,6 +14,7 @@

from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient, parse_json
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole
from common.djangoapps.student.models import CourseAccessRole
from common.djangoapps.student.tests.factories import UserFactory
from common.djangoapps.util.organizations_helpers import add_organization, get_course_organizations
from xmodule.course_module import CourseFields
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 registered in CourseAccessRole.
"""
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 access.
"""
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 common.djangoapps.student.models import CourseAccessRole
from common.djangoapps.student.tests.factories import UserFactory
from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService
from xmodule.modulestore import ModuleStoreEnum
Expand Down Expand Up @@ -829,6 +830,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_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
14 changes: 14 additions & 0 deletions cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,20 @@ 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:
log.exception(
"User does not have the permission to create course in this organization."
"User: {} Org: {} Course: {}".format(request.user.id, org, course)
)
return JsonResponse(
{'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
12 changes: 12 additions & 0 deletions cms/djangoapps/contentstore/views/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ 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."
"User: {} Org: {} Library: {}".format(request.user.id, org, library)
)
return JsonResponseBadRequest({
"ErrMsg": _(u"User does not have the permission to create library in this organization")
})
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 @@ -410,6 +410,21 @@
# .. toggle_warnings: Also set settings.LIBRARY_AUTHORING_MICROFRONTEND_URL and see
# REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND for rollout.
'ENABLE_LIBRARY_AUTHORING_MICROFRONTEND': False,

# .. 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