Skip to content

Commit

Permalink
Added global staff creator role, made appropriate changes for it's pe…
Browse files Browse the repository at this point in the history
…rmissions (#105)
  • Loading branch information
rehan99000 authored Jun 12, 2020
1 parent f1fb578 commit e988890
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 10 deletions.
22 changes: 15 additions & 7 deletions cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
from openedx.features.course_experience.waffle import waffle as course_experience_waffle
from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML
from openedx.features.edly.utils import get_enabled_organizations
from openedx.features.edly.utils import get_edx_org_from_cookie, get_enabled_organizations
from six import text_type

from contentstore.course_group_config import (
Expand Down Expand Up @@ -75,7 +75,14 @@
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from student import auth
from student.auth import has_course_author_access, has_studio_read_access, has_studio_write_access
from student.roles import CourseCreatorRole, CourseInstructorRole, CourseStaffRole, GlobalStaff, UserBasedRole
from student.roles import (
CourseCreatorRole,
CourseInstructorRole,
CourseStaffRole,
GlobalCourseCreatorRole,
GlobalStaff,
UserBasedRole,
)
from util.course import get_link_for_about_page
from util.date_utils import get_default_time_display
from util.json_request import JsonResponse, JsonResponseBadRequest, expect_json
Expand Down Expand Up @@ -334,9 +341,6 @@ def course_rerun_handler(request, course_key_string):
GET
html: return html page with form to rerun a course for the given course id
"""
# Only global staff (PMs) are able to rerun courses during the soft launch
if not GlobalStaff().has_user(request.user):
raise PermissionDenied()
course_key = CourseKey.from_string(course_key_string)
with modulestore().bulk_operations(course_key):
course_module = get_course_and_check_access(course_key, request.user, depth=3)
Expand Down Expand Up @@ -508,7 +512,8 @@ def filter_ccx(course_access):

instructor_courses = UserBasedRole(request.user, CourseInstructorRole.ROLE).courses_with_role()
staff_courses = UserBasedRole(request.user, CourseStaffRole.ROLE).courses_with_role()
all_courses = filter(filter_ccx, instructor_courses | staff_courses)
site_courses = UserBasedRole(request.user, GlobalCourseCreatorRole.ROLE).courses_with_role()
all_courses = filter(filter_ccx, instructor_courses | staff_courses | site_courses)
courses_list = []
course_keys = {}

Expand Down Expand Up @@ -555,6 +560,9 @@ def course_listing(request):
enabled_organizations = get_enabled_organizations(request)
org = enabled_organizations[0].get('short_name', '') if enabled_organizations else None

edly_user_info_cookie = request.COOKIES.get(settings.EDLY_USER_INFO_COOKIE_NAME, None)
org = get_edx_org_from_cookie(edly_user_info_cookie)

courses_iter, in_process_course_actions = get_courses_accessible_to_user(request, org)
user = request.user
libraries = _accessible_libraries_iter(request.user, org) if LIBRARIES_ENABLED else []
Expand Down Expand Up @@ -608,7 +616,7 @@ def format_library_for_view(library):
u'user': user,
u'request_course_creator_url': reverse('request_course_creator'),
u'course_creator_status': _get_course_creator_status(user),
u'rerun_creator_status': GlobalStaff().has_user(user),
u'rerun_creator_status': _get_course_creator_status(user),
u'allow_unicode_course_id': settings.FEATURES.get(u'ALLOW_UNICODE_COURSE_ID', False),
u'allow_course_reruns': settings.FEATURES.get(u'ALLOW_COURSE_RERUNS', True),
u'optimization_enabled': optimization_enabled
Expand Down
5 changes: 4 additions & 1 deletion common/djangoapps/student/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
CourseCreatorRole,
CourseInstructorRole,
CourseRole,
GlobalCourseCreatorRole,
CourseStaffRole,
GlobalStaff,
LibraryUserRole,
Expand Down Expand Up @@ -88,6 +89,8 @@ def get_user_permissions(user, course_key, org=None):
return all_perms
if course_key and user_has_role(user, CourseInstructorRole(course_key)):
return all_perms
if course_key and user_has_role(user, GlobalCourseCreatorRole(org)):
return all_perms
# Staff have all permissions except EDIT_ROLES:
if OrgStaffRole(org=org).has_user(user) or (course_key and user_has_role(user, CourseStaffRole(course_key))):
return STUDIO_VIEW_USERS | STUDIO_EDIT_CONTENT | STUDIO_VIEW_CONTENT
Expand Down Expand Up @@ -167,7 +170,7 @@ def _check_caller_authority(caller, role):
if not (caller.is_authenticated and caller.is_active):
raise PermissionDenied
# superuser
if GlobalStaff().has_user(caller):
if GlobalStaff().has_user(caller) or GlobalCourseCreatorRole().has_user(caller):
return

if isinstance(role, (GlobalStaff, CourseCreatorRole)):
Expand Down
17 changes: 17 additions & 0 deletions common/djangoapps/student/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,23 @@ def __repr__(self):
return '<{}>'.format(self.__class__.__name__)


@register_access_role
class GlobalCourseCreatorRole(OrgRole):
"""
A global course creator with access to all courses of the current site.
"""
ROLE = 'global_course_creator'

def __init__(self, *args, **kwargs):
"""
Initialization method for GlobalCourseCreatorRole.
Arguments:
org (str): Name of the organization of GlobalCourseCreatorRole
"""
super(GlobalCourseCreatorRole, self).__init__(self.ROLE, *args, **kwargs)


@register_access_role
class CourseStaffRole(CourseRole):
"""A Staff member of a course"""
Expand Down
70 changes: 69 additions & 1 deletion openedx/features/edly/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,39 @@
Tests for Edly Utils Functions.
"""
import jwt
import mock
from mock import MagicMock

from django.conf import settings
from django.http import HttpResponse
from django.test import TestCase
from django.test.client import RequestFactory

from openedx.core.djangolib.testing.utils import skip_unless_cms
from openedx.features.edly import cookies as cookies_api
from openedx.features.edly.tests.factories import EdlySubOrganizationFactory, EdlyUserProfileFactory, SiteFactory
from openedx.features.edly.utils import (
create_user_link_with_edly_sub_organization,
decode_edly_user_info_cookie,
encode_edly_user_info_cookie,
get_edly_sub_org_from_cookie,
user_has_edly_organization_access
get_edx_org_from_cookie,
set_global_course_creator_status,
update_course_creator_status,
user_has_edly_organization_access,
)
from student import auth
from student.roles import (
CourseCreatorRole,
GlobalCourseCreatorRole,
)
from student.tests.factories import UserFactory

def mock_render_to_string(template_name, context):
"""
Return a string that encodes template_name and context
"""
return str((template_name, context))

class UtilsTests(TestCase):
"""
Expand All @@ -32,6 +47,7 @@ def setUp(self):
"""
super(UtilsTests, self).setUp()
self.user = UserFactory.create()
self.admin_user = UserFactory.create(is_staff=True)
self.request = RequestFactory().get('/')
self.request.user = self.user
self.request.session = self._get_stub_session()
Expand Down Expand Up @@ -62,6 +78,14 @@ def _create_edly_sub_organization(self):
"""
return EdlySubOrganizationFactory(lms_site=self.request.site)

def _get_course_creator_status(self, user):
"""
Helper method to get user's course creator status.
"""
from course_creators.views import get_course_creator_status

return get_course_creator_status(user)

def test_encode_edly_user_info_cookie(self):
"""
Test that "encode_edly_user_info_cookie" method encodes data correctly.
Expand Down Expand Up @@ -114,6 +138,14 @@ def test_get_edly_sub_org_from_cookie(self):
edly_user_info_cookie = cookies_api._get_edly_user_info_cookie_string(self.request)
assert edly_sub_organization.slug == get_edly_sub_org_from_cookie(edly_user_info_cookie)

def test_get_edx_org_from_cookie(self):
"""
Test that "get_edx_org_from_cookie" method returns edx-org short name correctly.
"""
edly_sub_organization = self._create_edly_sub_organization()
edly_user_info_cookie = cookies_api._get_edly_user_info_cookie_string(self.request)
assert edly_sub_organization.edx_organization.short_name == get_edx_org_from_cookie(edly_user_info_cookie)

def test_create_user_link_with_edly_sub_organization(self):
"""
Test that "create_user_link_with_edly_sub_organization" method create "EdlyUserProfile" link with User.
Expand All @@ -123,3 +155,39 @@ def test_create_user_link_with_edly_sub_organization(self):
edly_user_profile = create_user_link_with_edly_sub_organization(self.request, user)
assert edly_user_profile == user.edly_profile
assert edly_sub_organization.slug in user.edly_profile.get_linked_edly_sub_organizations

@skip_unless_cms
@mock.patch('course_creators.admin.render_to_string', mock.Mock(side_effect=mock_render_to_string, autospec=True))
def test_update_course_creator_status(self):
"""
Test that "update_course_creator_status" method sets/removes a User as Course Creator correctly.
"""
settings.FEATURES['ENABLE_CREATOR_GROUP'] = True
update_course_creator_status(self.admin_user, self.user, True)
assert self._get_course_creator_status(self.user) == 'granted'
assert auth.user_has_role(self.user, CourseCreatorRole())

update_course_creator_status(self.admin_user, self.user, False)
assert self._get_course_creator_status(self.user) == 'unrequested'
assert not auth.user_has_role(self.user, CourseCreatorRole())

@skip_unless_cms
@mock.patch('course_creators.admin.render_to_string', mock.Mock(side_effect=mock_render_to_string, autospec=True))
def test_set_global_course_creator_status(self):
"""
Test that "set_global_course_creator_status" method sets/removes a User as Global Course Creator correctly.
"""
self._create_edly_sub_organization()
response = cookies_api.set_logged_in_edly_cookies(self.request, HttpResponse(), self.user)
self._copy_cookies_to_request(response, self.request)
edly_user_info_cookie = self.request.COOKIES.get(settings.EDLY_USER_INFO_COOKIE_NAME)
edx_org = get_edx_org_from_cookie(edly_user_info_cookie)
self.request.user = self.admin_user

set_global_course_creator_status(self.request, self.user, True)
assert self._get_course_creator_status(self.user) == 'granted'
assert auth.user_has_role(self.user, GlobalCourseCreatorRole(edx_org))

set_global_course_creator_status(self.request, self.user, False)
assert self._get_course_creator_status(self.user) == 'unrequested'
assert not auth.user_has_role(self.user, GlobalCourseCreatorRole(edx_org))
57 changes: 56 additions & 1 deletion openedx/features/edly/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
from django.forms.models import model_to_dict

from openedx.features.edly.models import EdlyUserProfile, EdlySubOrganization
from student import auth
from student.roles import (
CourseInstructorRole,
CourseStaffRole,
GlobalCourseCreatorRole,
UserBasedRole,
)
from util.organizations_helpers import get_organizations

LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -91,6 +98,24 @@ def get_edly_sub_org_from_cookie(encoded_cookie_data):
return decoded_cookie_data['edly-sub-org']


def get_edx_org_from_cookie(encoded_cookie_data):
"""
Returns edx-org short name from the edly-user-info cookie.
Arguments:
encoded_cookie_data (dict): Edly user info cookie JWT encoded string.
Returns:
string
"""

if not encoded_cookie_data:
return ''

decoded_cookie_data = decode_edly_user_info_cookie(encoded_cookie_data)
return decoded_cookie_data['edx-org']


def get_enabled_organizations(request):
"""
Helper method to get linked organizations for request site.
Expand Down Expand Up @@ -139,8 +164,38 @@ def update_course_creator_status(request_user, user, set_creator):
Updates course creator status of a user.
"""
from course_creators.models import CourseCreator
from course_creators.views import update_course_creator_group

course_creator, __ = CourseCreator.objects.get_or_create(user=user)
course_creator.state = CourseCreator.GRANTED if set_creator else CourseCreator.DENIED
course_creator.state = CourseCreator.GRANTED if set_creator else CourseCreator.UNREQUESTED
course_creator.note = 'Course creator user was updated by panel admin {}'.format(request_user.email)
course_creator.admin = request_user
course_creator.save()
if not set_creator:
update_course_creator_group(request_user, user, set_creator)
instructor_courses = UserBasedRole(user, CourseInstructorRole.ROLE).courses_with_role()
staff_courses = UserBasedRole(user, CourseStaffRole.ROLE).courses_with_role()
instructor_courses_keys = [course.course_id for course in instructor_courses]
staff_courses_keys = [course.course_id for course in staff_courses]
UserBasedRole(user, CourseInstructorRole.ROLE).remove_courses(*instructor_courses_keys)
UserBasedRole(user, CourseStaffRole.ROLE).remove_courses(*staff_courses_keys)


def set_global_course_creator_status(request, user, set_global_creator):
"""
Updates global course creator status of a user.
"""
from course_creators.models import CourseCreator

request_user = request.user
course_creator, __ = CourseCreator.objects.get_or_create(user=user)
course_creator.state = CourseCreator.GRANTED if set_global_creator else CourseCreator.UNREQUESTED
course_creator.note = 'Global course creator user was updated by panel admin {}'.format(request_user.email)
course_creator.admin = request_user
course_creator.save()
edly_user_info_cookie = request.COOKIES.get(settings.EDLY_USER_INFO_COOKIE_NAME, None)
edx_org = get_edx_org_from_cookie(edly_user_info_cookie)
if set_global_creator:
GlobalCourseCreatorRole(edx_org).add_users(user)
else:
GlobalCourseCreatorRole(edx_org).remove_users(user)

0 comments on commit e988890

Please sign in to comment.