def _check_caller_authority(caller, role): """ Internal function to check whether the caller has authority to manipulate this role :param caller: a user :param role: an AccessRole """ if not (caller.is_authenticated() and caller.is_active): raise PermissionDenied # superuser if GlobalStaff().has_user(caller): return if isinstance(role, (GlobalStaff, CourseCreatorRole)): raise PermissionDenied elif isinstance( role, CourseRole): # instructors can change the roles w/in their course if not user_has_role(caller, CourseInstructorRole(role.course_key)): raise PermissionDenied
def get_ability(course_id, content, user): """ Return a dictionary of forums-oriented actions and the user's permission to perform them """ (user_group_id, content_user_group_id) = get_user_group_ids(course_id, content, user) return { 'editable': check_permissions_by_view( user, course_id, content, "update_thread" if content['type'] == 'thread' else "update_comment", user_group_id, content_user_group_id), 'can_reply': check_permissions_by_view( user, course_id, content, "create_comment" if content['type'] == 'thread' else "create_sub_comment", ), 'can_delete': check_permissions_by_view( user, course_id, content, "delete_thread" if content['type'] == 'thread' else "delete_comment", user_group_id, content_user_group_id), 'can_openclose': check_permissions_by_view( user, course_id, content, "openclose_thread" if content['type'] == 'thread' else False, user_group_id, content_user_group_id), 'can_vote': not is_content_authored_by(content, user) and check_permissions_by_view( user, course_id, content, "vote_for_thread" if content['type'] == 'thread' else "vote_for_comment"), 'can_report': not is_content_authored_by(content, user) and (check_permissions_by_view( user, course_id, content, "flag_abuse_for_thread" if content['type'] == 'thread' else "flag_abuse_for_comment") or GlobalStaff().has_user(user)) }
def course_listing(request): """ List all courses available to the logged in user Try to get all courses by first reversing django groups and fallback to old method if it fails Note: overhead of pymongo reads will increase if getting courses from django groups fails """ if GlobalStaff().has_user(request.user): # user has global access so no need to get courses from django groups courses = _accessible_courses_list(request) else: try: courses = _accessible_courses_list_from_groups(request) except ItemNotFoundError: # user have some old groups or there was some error getting courses from django groups # so fallback to iterating through all courses courses = _accessible_courses_list(request) def format_course_for_view(course): """ return tuple of the data which the view requires for each course """ return (course.display_name, reverse_course_url('course_handler', course.id), get_lms_link_for_item(course.location), course.display_org_with_default, course.display_number_with_default, course.location.name) return render_to_response( 'index.html', { 'courses': [ format_course_for_view(c) for c in courses if not isinstance(c, ErrorDescriptor) ], 'user': request.user, 'request_course_creator_url': reverse('contentstore.views.request_course_creator'), 'course_creator_status': _get_course_creator_status(request.user), 'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False) })
def _has_access_to_course(user, access_level, course_key): """ Returns True if the given user has access_level (= staff or instructor) access to the course with the given course_key. This ensures the user is authenticated and checks if global staff or has staff / instructor access. access_level = string, either "staff" or "instructor" """ if user is None or (not user.is_authenticated()): debug("Deny: no user or anon user") return ACCESS_DENIED if is_masquerading_as_student(user, course_key): return ACCESS_DENIED if GlobalStaff().has_user(user): debug("Allow: user.is_staff") return ACCESS_GRANTED if access_level not in ('staff', 'instructor'): log.debug( "Error in access._has_access_to_course access_level=%s unknown", access_level) debug("Deny: unknown access level") return ACCESS_DENIED staff_access = (CourseStaffRole(course_key).has_user(user) or OrgStaffRole(course_key.org).has_user(user)) if staff_access and access_level == 'staff': debug("Allow: user has course staff access") return ACCESS_GRANTED instructor_access = (CourseInstructorRole(course_key).has_user(user) or OrgInstructorRole(course_key.org).has_user(user)) if instructor_access and access_level in ('staff', 'instructor'): debug("Allow: user has course instructor access") return ACCESS_GRANTED debug("Deny: user did not have correct access") return ACCESS_DENIED
def list_course_keys(request, username, role): """ Yield all available CourseKeys for the user having the given role. The courses returned include those for which the user identified by `username` has the given role. Additionally, the logged in user should have permission to view courses available to that user. Note: This function does not use branding to determine courses. Arguments: request (HTTPRequest): Used to identify the logged-in user and to instantiate the course module to retrieve the course about description username (string): The name of the user the logged-in user would like to be identified as Keyword Arguments: role (string): Course keys are filtered such that only those for which the user has the specified role are returned. Return value: Yield `CourseKey` objects representing the collection of courses. """ user = get_effective_user(request.user, username) course_keys = CourseOverview.get_all_course_keys() # Global staff have access to all courses. Filter courses for non-global staff. if GlobalStaff().has_user(user): return course_keys return LazySequence( ( course_key for course_key in course_keys if has_access(user, role, course_key) ), est_len=len(course_keys) )
def setUp(self): """ Add courses with the end date set to various values """ super(TestCourseIndexArchived, self).setUp() # Base course has no end date (so is active) self.course.end = None self.course.display_name = 'Active Course 1' self.ORG = self.course.location.org self.save_course() # Active course has end date set to tomorrow self.active_course = CourseFactory.create( display_name='Active Course 2', org=self.ORG, end=self.TOMORROW, ) # Archived course has end date set to yesterday self.archived_course = CourseFactory.create( display_name='Archived Course', org=self.ORG, end=self.YESTERDAY, ) # Base user has global staff access self.assertTrue(GlobalStaff().has_user(self.user)) # Staff user just has course staff access self.staff, self.staff_password = self.create_non_staff_user() for course in (self.course, self.active_course, self.archived_course): CourseStaffRole(course.id).add_users(self.staff) # Make sure we've cached data which could change the query counts # depending on test execution order WaffleSwitchNamespace(name=COURSE_WAFFLE_NAMESPACE).is_enabled( u'enable_global_staff_optimization') WaffleSwitchNamespace( name=STUDIO_WAFFLE_NAMESPACE).is_enabled(u'enable_policy_page') WaffleSwitchNamespace(name=DJANGO_UTILS_NAMESPACE).is_enabled( u'enable_memory_middleware')
def _check_caller_authority(caller, role): """ Internal function to check whether the caller has authority to manipulate this role :param caller: a user :param role: an AccessRole """ if not (caller.is_authenticated and caller.is_active): raise PermissionDenied # superuser if GlobalStaff().has_user(caller) or caller.groups.filter( name=settings.EDLY_PANEL_ADMIN_USERS_GROUP).exists(): return if isinstance(role, (GlobalStaff, CourseCreatorRole)): raise PermissionDenied elif isinstance( role, CourseRole): # instructors can change the roles w/in their course if not user_has_role(caller, CourseInstructorRole(role.course_key)): raise PermissionDenied
def test_errored_course_global_staff(self, store, path_to_patch): """ Test the course list for global staff when get_course returns an ErrorDescriptor """ GlobalStaff().add_users(self.user) with self.store.default_store(store): course_key = self.store.make_course_key('Org1', 'Course1', 'Run1') self._create_course_with_access_groups(course_key, self.user, store=store) with patch(path_to_patch, Mock(side_effect=Exception)): self.assertIsInstance(self.store.get_course(course_key), ErrorDescriptor) # get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(courses_list, []) # get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request) self.assertEqual(courses_list_by_groups, [])
def _get_courses_with_access_type(self, user, access_type): # Check the application cache and update if not present. The application # cache is useful since there are calls to different endpoints in close # succession, for example the id_token and user_info endpoints. key = '-'.join([str(self.__class__), str(user.id), access_type]) course_ids = cache.get(key) if not course_ids: course_keys = CourseOverview.get_all_course_keys() # Global staff have access to all courses. Filter courses for non-global staff. if not GlobalStaff().has_user(user): course_keys = [course_key for course_key in course_keys if has_access(user, access_type, course_key)] course_ids = [six.text_type(course_key) for course_key in course_keys] cache.set(key, course_ids, self.COURSE_CACHE_TIMEOUT) return course_ids
def test_errored_course_regular_access(self): """ Test the course list for regular staff when get_course returns an ErrorDescriptor """ GlobalStaff().remove_users(self.user) CourseStaffRole(SlashSeparatedCourseKey('Non', 'Existent', 'Course')).add_users(self.user) course_key = SlashSeparatedCourseKey('Org1', 'Course1', 'Run1') self._create_course_with_access_groups(course_key, self.user) with patch('xmodule.modulestore.mongo.base.MongoKeyValueStore', Mock(side_effect=Exception)): self.assertIsInstance(modulestore().get_course(course_key), ErrorDescriptor) # get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(courses_list, []) # get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request) self.assertEqual(courses_list_by_groups, []) self.assertEqual(courses_list, courses_list_by_groups)
def get_user_permissions(user, course_key, org=None): """ Get the bitmask of permissions that this user has in the given course context. Can also set course_key=None and pass in an org to get the user's permissions for that organization as a whole. """ if org is None: org = course_key.org course_key = course_key.for_branch(None) else: assert course_key is None # No one has studio permissions for CCX courses if is_ccx_course(course_key): return STUDIO_NO_PERMISSIONS all_perms = STUDIO_EDIT_ROLES | STUDIO_VIEW_USERS | STUDIO_EDIT_CONTENT | STUDIO_VIEW_CONTENT # global staff, org instructors, and course instructors have all permissions: if GlobalStaff().has_user(user) or OrgInstructorRole( org=org).has_user(user): return all_perms if course_key and user_has_role(user, CourseInstructorRole(course_key)): 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 # Otherwise, for libraries, users can view only: if course_key and isinstance(course_key, LibraryLocator): if OrgLibraryUserRole(org=org).has_user(user) or user_has_role( user, LibraryUserRole(course_key)): return STUDIO_VIEW_USERS | STUDIO_VIEW_CONTENT # Finally, check if user is linked directly to the organization: user_org = OrganizationUser.objects.filter( active=True, organization__short_name=org, user_id=user.id).values().first() if user_org: if user_org['is_staff']: return all_perms else: return STUDIO_EDIT_CONTENT | STUDIO_VIEW_CONTENT return STUDIO_NO_PERMISSIONS
def get(self, request): """Gets a list of all course enrollments for a user. Returns a list for the currently logged in user, or for the user named by the 'user' GET parameter. If the username does not match that of the currently logged in user, only courses for which the currently logged in user has the Staff or Admin role are listed. As a result, a course team member can find out which of his or her own courses a particular learner is enrolled in. Only the Staff or Admin role (granted on the Django administrative console as the staff or instructor permission) in individual courses gives the requesting user access to enrollment data. Permissions granted at the organizational level do not give a user access to enrollment data for all of that organization's courses. Users who have the global staff permission can access all enrollment data for all courses. """ username = request.GET.get('user', request.user.username) try: enrollment_data = api.get_enrollments(username) print enrollment_data except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"An error occurred while retrieving enrollments for user '{username}'" ).format(username=username) }) if username == request.user.username or GlobalStaff().has_user(request.user) or \ self.has_api_key_permissions(request): return Response(enrollment_data) filtered_data = [] for enrollment in enrollment_data: course_key = CourseKey.from_string( enrollment["course_details"]["course_id"]) if user_has_role(request.user, CourseStaffRole(course_key)): filtered_data.append(enrollment) return Response(filtered_data)
def has_course_author_access(user, course_key, role=CourseStaffRole): """ Return True if user has studio (write) access to the given course. Note that the CMS permissions model is with respect to courses. There is a super-admin permissions if user.is_staff is set. Also, since we're unifying the user database between LMS and CAS, I'm presuming that the course instructor (formally known as admin) will not be in both INSTRUCTOR and STAFF groups, so we have to cascade our queries here as INSTRUCTOR has all the rights that STAFF do. :param user: :param course_key: a CourseKey :param role: an AccessRole """ if GlobalStaff().has_user(user): return True if OrgInstructorRole(org=course_key.org).has_user(user): return True if OrgStaffRole(org=course_key.org).has_user(user): return True # temporary to ensure we give universal access given a course until we impl branch specific perms return has_access(user, role(course_key.for_branch(None)))
def has_staff_roles(user, course_key): """ Return true if a user has any of the following roles Staff, Instructor, Beta Tester, Forum Community TA, Forum Group Moderator, Forum Moderator, Forum Administrator """ forum_roles = [ FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_GROUP_MODERATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_ADMINISTRATOR ] is_staff = CourseStaffRole(course_key).has_user(user) is_instructor = CourseInstructorRole(course_key).has_user(user) is_beta_tester = CourseBetaTesterRole(course_key).has_user(user) is_org_staff = OrgStaffRole(course_key.org).has_user(user) is_org_instructor = OrgInstructorRole(course_key.org).has_user(user) is_global_staff = GlobalStaff().has_user(user) has_forum_role = Role.user_has_role_for_course(user, course_key, forum_roles) if any([ is_staff, is_instructor, is_beta_tester, is_org_staff, is_org_instructor, is_global_staff, has_forum_role ]): return True return False
def get_jwt_token(self, request, suffix=''): """Generate JWT token for SSO authorization""" annoto_auth = self.get_annoto_settings() if not annoto_auth: msg = self.i18n_service.gettext( 'Annoto authorization is not provided in "LTI Passports".') return self._json_resp({'status': 'error', 'msg': msg}) user = User.objects.get( id=self.runtime.service(self, 'user').get_current_user( ).opt_attrs.get('edx-platform.user_id')) if not user: msg = self.i18n_service.gettext('Requested user does not exists.') return self._json_resp({'status': 'error', 'msg': msg}) roles = user.courseaccessrole_set.filter( course_id=self.course_id).values_list('role', flat=True) if CourseStaffRole.ROLE in roles or GlobalStaff().has_user(user): scope = 'super-mod' elif CourseInstructorRole.ROLE in roles: scope = 'moderator' else: scope = 'user' payload = { 'exp': int(time.time() + 60 * 20), 'iss': annoto_auth['client_id'], 'jti': user.id, 'name': user.username, 'scope': scope } token = jwt.encode(payload, annoto_auth['client_secret'], algorithm='HS256') return self._json_resp({'status': 'ok', 'token': token})
def check_support(): """Check that the user has access to the support UI. """ if perm != 'global': return ACCESS_DENIED return (ACCESS_GRANTED if GlobalStaff().has_user(user) or SupportStaffRole().has_user(user) else ACCESS_DENIED)
def set_roles_for_edx_users(user, permissions, strategy): ''' This function is specific functional for open-edx platform. It create roles for edx users from sso permissions. ''' log_message = 'For User: {}, object_type {} and object_id {} there is not matched Role for Permission set: {}' global_perm = { 'Read', 'Update', 'Delete', 'Publication', 'Enroll', 'Manage(permissions)' } staff_perm = {'Read', 'Update', 'Delete', 'Publication', 'Enroll'} tester_perm = {'Read', 'Enroll'} role_ids = set(user.courseaccessrole_set.values_list('id', flat=True)) new_role_ids = [] is_global_staff = False for role in permissions: _log = False if role['obj_type'] == '*': if '*' in role['obj_perm'] or global_perm.issubset( set(role['obj_perm'])): GlobalStaff().add_users(user) is_global_staff = True elif 'Create' in role['obj_perm']: if not CourseCreatorRole().has_user(user): CourseCreatorRole().add_users(user) car = CourseAccessRole.objects.get(user=user, role=CourseCreatorRole.ROLE) new_role_ids.append(car.id) if role['obj_perm'] != '*' and global_perm != set( role['obj_perm']) and ['Create'] != role['obj_perm']: _log = True elif role['obj_type'] == 'edxorg': if '*' in role['obj_perm'] or global_perm.issubset( set(role['obj_perm'])): if not OrgInstructorRole(role['obj_id']).has_user(user): OrgInstructorRole(role['obj_id']).add_users(user) car = CourseAccessRole.objects.get( user=user, role=OrgInstructorRole(role['obj_id'])._role_name, org=role['obj_id']) new_role_ids.append(car.id) elif staff_perm.issubset(set(role['obj_perm'])): if not OrgStaffRole(role['obj_id']).has_user(user): OrgStaffRole(role['obj_id']).add_users(user) car = CourseAccessRole.objects.get( user=user, role=OrgStaffRole(role['obj_id'])._role_name, org=role['obj_id']) new_role_ids.append(car.id) elif 'Read' in role['obj_perm']: if not OrgLibraryUserRole(role['obj_id']).has_user(user): OrgLibraryUserRole(role['obj_id']).add_users(user) car = CourseAccessRole.objects.get( user=user, role=OrgLibraryUserRole.ROLE, org=role['obj_id']) new_role_ids.append(car.id) if role['obj_perm'] != '*' and global_perm != set(role['obj_perm']) and \ staff_perm != set(role['obj_perm']) and 'Read' not in role['obj_perm']: _log = True elif role['obj_type'] in ['edxcourse', 'edxlibrary']: course_key = CourseKey.from_string(role['obj_id']) if '*' in role['obj_perm'] or global_perm.issubset( set(role['obj_perm'])): if not CourseInstructorRole(course_key).has_user(user): CourseInstructorRole(course_key).add_users(user) car = CourseAccessRole.objects.get( user=user, role=CourseInstructorRole.ROLE, course_id=course_key) new_role_ids.append(car.id) elif staff_perm.issubset(set(role['obj_perm'])): if not CourseStaffRole(course_key).has_user(user): CourseStaffRole(course_key).add_users(user) car = CourseAccessRole.objects.get(user=user, role=CourseStaffRole.ROLE, course_id=course_key) new_role_ids.append(car.id) elif tester_perm.issubset(set(role['obj_perm'])): if not CourseBetaTesterRole(course_key).has_user(user): CourseBetaTesterRole(course_key).add_users(user) car = CourseAccessRole.objects.get( user=user, role=CourseBetaTesterRole.ROLE, course_id=course_key) new_role_ids.append(car.id) elif role['obj_type'] == 'edxlibrary' and 'Read' in role[ 'obj_perm']: if not LibraryUserRole(course_key).has_user(user): LibraryUserRole(course_key).add_users(user) car = CourseAccessRole.objects.get( user=user, role=CourseBetaTesterRole.ROLE, course_id=course_key) new_role_ids.append(car.id) if role['obj_perm'] != '*' and global_perm != set(role['obj_perm']) and \ staff_perm != set(role['obj_perm']) and tester_perm != set(role['obj_perm']) and 'Read' not in role['obj_perm']: _log = True elif role['obj_type'] == 'edxcourserun': course_key = CourseKey.from_string(role['obj_id']) if '*' in role['obj_perm'] or global_perm.issubset( set(role['obj_perm'])): if not CourseInstructorRole(course_key).has_user(user): CourseInstructorRole(course_key).add_users(user) car = CourseAccessRole.objects.get( user=user, role=CourseInstructorRole.ROLE, course_id=course_key) new_role_ids.append(car.id) elif staff_perm.issubset(set(role['obj_perm'])): if not CourseStaffRole(course_key).has_user(user): CourseStaffRole(course_key).add_users(user) car = CourseAccessRole.objects.get(user=user, role=CourseStaffRole.ROLE, course_id=course_key) new_role_ids.append(car.id) elif tester_perm.issubset(set(role['obj_perm'])): if not CourseBetaTesterRole(course_key).has_user(user): CourseBetaTesterRole(course_key).add_users(user) car = CourseAccessRole.objects.get( user=user, role=CourseBetaTesterRole.ROLE, course_id=course_key) new_role_ids.append(car.id) if role['obj_perm'] != '*' and global_perm != set(role['obj_perm']) and \ staff_perm != set(role['obj_perm']) and tester_perm != set(role['obj_perm']): _log = True if _log: logging.warning( log_message.format(user.id, role['obj_type'], role['obj_id'], str(role['obj_perm']))) if (not is_global_staff) and GlobalStaff().has_user(user): GlobalStaff().remove_users(user) remove_roles = role_ids - set(new_role_ids) if remove_roles: entries = CourseAccessRole.objects.filter(id__in=list(remove_roles)) entries.delete()
def certificates_detail_handler(request, course_key_string, certificate_id): """ JSON API endpoint for manipulating a course certificate via its internal identifier. Utilized by the Backbone.js 'certificates' application model POST or PUT json: update the specified certificate based on provided information DELETE json: remove the specified certificate from the course """ course_key = CourseKey.from_string(course_key_string) course = _get_course_and_check_access(course_key, request.user) certificates_list = course.certificates.get('certificates', []) match_index = None match_cert = None for index, cert in enumerate(certificates_list): if certificate_id is not None: if int(cert['id']) == int(certificate_id): match_index = index match_cert = cert store = modulestore() if request.method in ('POST', 'PUT'): if certificate_id: active_certificates = CertificateManager.get_certificates( course, only_active=True) if int(certificate_id) in [ int(certificate["id"]) for certificate in active_certificates ]: # Only global staff (PMs) are able to edit active certificate configuration if not GlobalStaff().has_user(request.user): raise PermissionDenied() try: new_certificate = CertificateManager.deserialize_certificate( course, request.body) except CertificateValidationError as err: return JsonResponse({"error": text_type(err)}, status=400) serialized_certificate = CertificateManager.serialize_certificate( new_certificate) cert_event_type = 'created' if match_cert: cert_event_type = 'modified' certificates_list[match_index] = serialized_certificate else: certificates_list.append(serialized_certificate) store.update_item(course, request.user.id) CertificateManager.track_event( cert_event_type, { 'course_id': unicode(course.id), 'configuration_id': serialized_certificate["id"] }) return JsonResponse(serialized_certificate, status=201) elif request.method == "DELETE": if not match_cert: return JsonResponse(status=404) active_certificates = CertificateManager.get_certificates( course, only_active=True) if int(certificate_id) in [ int(certificate["id"]) for certificate in active_certificates ]: # Only global staff (PMs) are able to delete active certificate configuration if not GlobalStaff().has_user(request.user): raise PermissionDenied() CertificateManager.remove_certificate(request=request, store=store, course=course, certificate_id=certificate_id) CertificateManager.track_event('deleted', { 'course_id': unicode(course.id), 'configuration_id': certificate_id }) return JsonResponse(status=204)
def _setstaff_login(self): """Makes the test user staff and logs them in""" GlobalStaff().add_users(self.user) self.client.login(username=self.user.username, password='******')
def get_exclude_list_of_fields(cls, course_key): """ Returns a list of fields to exclude from the Studio Advanced settings based on a feature flag (i.e. enabled or disabled). """ # Copy the filtered list to avoid permanently changing the class attribute. exclude_list = list(cls.FIELDS_EXCLUDE_LIST) # Do not show giturl if feature is not enabled. if not settings.FEATURES.get('ENABLE_EXPORT_GIT'): exclude_list.append('giturl') # Do not show edxnotes if the feature is disabled. if not settings.FEATURES.get('ENABLE_EDXNOTES'): exclude_list.append('edxnotes') # Do not show video auto advance if the feature is disabled if not settings.FEATURES.get('ENABLE_OTHER_COURSE_SETTINGS'): exclude_list.append('other_course_settings') # Do not show video_upload_pipeline if the feature is disabled. if not settings.FEATURES.get('ENABLE_VIDEO_UPLOAD_PIPELINE'): exclude_list.append('video_upload_pipeline') # Do not show video auto advance if the feature is disabled if not settings.FEATURES.get('ENABLE_AUTOADVANCE_VIDEOS'): exclude_list.append('video_auto_advance') # Do not show social sharing url field if the feature is disabled. if (not hasattr(settings, 'SOCIAL_SHARING_SETTINGS') or not getattr(settings, 'SOCIAL_SHARING_SETTINGS', {}).get("CUSTOM_COURSE_URLS")): exclude_list.append('social_sharing_url') # Do not show teams configuration if feature is disabled. if not settings.FEATURES.get('ENABLE_TEAMS'): exclude_list.append('teams_configuration') if not settings.FEATURES.get('ENABLE_VIDEO_BUMPER'): exclude_list.append('video_bumper') # Do not show enable_ccx if feature is not enabled. if not settings.FEATURES.get('CUSTOM_COURSES_EDX'): exclude_list.append('enable_ccx') exclude_list.append('ccx_connector') # Do not show "Issue Open Badges" in Studio Advanced Settings # if the feature is disabled. if not settings.FEATURES.get('ENABLE_OPENBADGES'): exclude_list.append('issue_badges') # If the XBlockStudioConfiguration table is not being used, there is no need to # display the "Allow Unsupported XBlocks" setting. if not XBlockStudioConfigurationFlag.is_enabled(): exclude_list.append('allow_unsupported_xblocks') # Do not show "Course Visibility For Unenrolled Learners" in Studio Advanced Settings # if the enable_anonymous_access flag is not enabled if not COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key=course_key): exclude_list.append('course_visibility') # Do not show "Create Zendesk Tickets For Suspicious Proctored Exam Attempts" in # Studio Advanced Settings if the user is not edX staff. if not GlobalStaff().has_user(get_current_user()): exclude_list.append('create_zendesk_tickets') # Do not show "Proctortrack Exam Escalation Contact" if Proctortrack is not # an available proctoring backend. if not settings.PROCTORING_BACKENDS or settings.PROCTORING_BACKENDS.get('proctortrack') is None: exclude_list.append('proctoring_escalation_email') return exclude_list
def certificates_list_handler(request, course_key_string): """ A RESTful handler for Course Certificates GET html: return Certificates list page (Backbone application) POST json: create new Certificate """ course_key = CourseKey.from_string(course_key_string) store = modulestore() with store.bulk_operations(course_key): try: course = _get_course_and_check_access(course_key, request.user) except PermissionDenied: msg = _('PermissionDenied: Failed in authenticating {user}' ).format(user=request.user) return JsonResponse({"error": msg}, status=403) if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'): certificate_url = reverse_course_url('certificates_list_handler', course_key) course_outline_url = reverse_course_url('course_handler', course_key) upload_asset_url = reverse_course_url('assets_handler', course_key) activation_handler_url = reverse_course_url( handler_name='certificate_activation_handler', course_key=course_key) course_modes = [ mode.slug for mode in CourseMode.modes_for_course(course_id=course.id, include_expired=True) if mode.slug != 'audit' ] has_certificate_modes = len(course_modes) > 0 if has_certificate_modes: certificate_web_view_url = get_lms_link_for_certificate_web_view( user_id=request.user.id, course_key=course_key, mode=course_modes[ 0] # CourseMode.modes_for_course returns default mode if doesn't find anyone. ) else: certificate_web_view_url = None is_active, certificates = CertificateManager.is_activated(course) return render_to_response( 'certificates.html', { 'context_course': course, 'certificate_url': certificate_url, 'course_outline_url': course_outline_url, 'upload_asset_url': upload_asset_url, 'certificates': certificates, 'has_certificate_modes': has_certificate_modes, 'course_modes': course_modes, 'certificate_web_view_url': certificate_web_view_url, 'is_active': is_active, 'is_global_staff': GlobalStaff().has_user(request.user), 'certificate_activation_handler_url': activation_handler_url }) elif "application/json" in request.META.get('HTTP_ACCEPT'): # Retrieve the list of certificates for the specified course if request.method == 'GET': certificates = CertificateManager.get_certificates(course) return JsonResponse(certificates, encoder=EdxJSONEncoder) elif request.method == 'POST': # Add a new certificate to the specified course try: new_certificate = CertificateManager.deserialize_certificate( course, request.body) except CertificateValidationError as err: return JsonResponse({"error": text_type(err)}, status=400) if course.certificates.get('certificates') is None: course.certificates['certificates'] = [] course.certificates['certificates'].append( new_certificate.certificate_data) response = JsonResponse( CertificateManager.serialize_certificate(new_certificate), status=201) response["Location"] = reverse_course_url( 'certificates_detail_handler', course.id, kwargs={'certificate_id': new_certificate.id}) store.update_item(course, request.user.id) CertificateManager.track_event( 'created', { 'course_id': unicode(course.id), 'configuration_id': new_certificate.id }) course = _get_course_and_check_access(course_key, request.user) return response else: return HttpResponse(status=406)
def list_course_keys(request, username, role): """ Yield all available CourseKeys for the user having the given role. The courses returned include those for which the user identified by `username` has the given role. Additionally, the logged in user should have permission to view courses available to that user. Note: This function does not use branding to determine courses. Arguments: request (HTTPRequest): Used to identify the logged-in user and to instantiate the course module to retrieve the course about description username (string): The name of the user the logged-in user would like to be identified as Keyword Arguments: role (string): Course keys are filtered such that only those for which the user has the specified role are returned. Return value: Yield `CourseKey` objects representing the collection of courses. """ user = get_effective_user(request.user, username) all_course_keys = CourseOverview.get_all_course_keys() # Global staff have access to all courses. Filter courses for non-global staff. if GlobalStaff().has_user(user): return all_course_keys if role == 'staff': # This short-circuit implementation bypasses has_access() which we think is too slow for some users when # evaluating staff-level course access for Insights. Various tickets have context on this issue: CR-2487, # TNL-7448, DESUPPORT-416, and probably more. # # This is a simplified implementation that does not consider org-level access grants (e.g. when course_id is # empty). filtered_course_keys = ( CourseAccessRole.objects.filter( user=user, # Having the instructor role implies staff access. role__in=['staff', 'instructor'], ) # We need to check against CourseOverview so that we don't return any Libraries. .extra(tables=['course_overviews_courseoverview'], where=['course_id = course_overviews_courseoverview.id']) # For good measure, make sure we don't return empty course IDs. .exclude(course_id=CourseKeyField.Empty).order_by( 'course_id').values_list('course_id', flat=True).distinct()) else: # This is the original implementation which still covers the case where role = "instructor": filtered_course_keys = LazySequence( (course_key for course_key in all_course_keys if has_access(user, role, course_key)), est_len=len(all_course_keys)) return filtered_course_keys
def set_staff(self, create, extracted, **kwargs): GlobalStaff().add_users(self)
def check_staff(): if perm != 'global': debug("Deny: invalid permission '%s'", perm) return False return GlobalStaff().has_user(user)
def test_global_staff(self): self.assertFalse(GlobalStaff().has_user(self.student)) self.assertFalse(GlobalStaff().has_user(self.course_staff)) self.assertFalse(GlobalStaff().has_user(self.course_instructor)) self.assertTrue(GlobalStaff().has_user(self.global_staff))
def post(self, request): # pylint: disable=too-many-statements """Enrolls the currently logged-in user in a course. Server-to-server calls may deactivate or modify the mode of existing enrollments. All other requests go through `add_enrollment()`, which allows creation of new and reactivation of old enrollments. """ # Get the User, Course ID, and Mode from the request. username = request.data.get('user', request.user.username) course_id = request.data.get('course_details', {}).get('course_id') if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"Course ID must be specified to create a new enrollment." }) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) }) mode = request.data.get('mode') has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. if not username: username = request.user.username if username != request.user.username and not has_api_key_permissions \ and not GlobalStaff().has_user(request.user): # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if mode not in (CourseMode.AUDIT, CourseMode.HONOR, None) and not has_api_key_permissions \ and not GlobalStaff().has_user(request.user): return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": u"User does not have permission to create enrollment with mode [{mode}]." .format(mode=mode) }) try: # Lookup the user, instead of using request.user, since request.user may not match the username POSTed. user = User.objects.get(username=username) except ObjectDoesNotExist: return Response(status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(username) }) embargo_response = embargo_api.get_embargo_response( request, course_id, user) if embargo_response: return embargo_response try: is_active = request.data.get('is_active') # Check if the requested activation status is None or a Boolean if is_active is not None and not isinstance(is_active, bool): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': (u"'{value}' is an invalid enrollment activation status." ).format(value=is_active) }) explicit_linked_enterprise = request.data.get( 'linked_enterprise_customer') if explicit_linked_enterprise and has_api_key_permissions and enterprise_enabled( ): enterprise_api_client = EnterpriseApiServiceClient() consent_client = ConsentApiServiceClient() try: enterprise_api_client.post_enterprise_course_enrollment( username, text_type(course_id), None) except EnterpriseApiException as error: log.exception( u"An unexpected error occurred while creating the new EnterpriseCourseEnrollment " u"for user [%s] in course run [%s]", username, course_id) raise CourseEnrollmentError(text_type(error)) kwargs = { 'username': username, 'course_id': text_type(course_id), 'enterprise_customer_uuid': explicit_linked_enterprise, } consent_client.provide_consent(**kwargs) enrollment_attributes = request.data.get('enrollment_attributes') enrollment = api.get_enrollment(username, text_type(course_id)) mode_changed = enrollment and mode is not None and enrollment[ 'mode'] != mode active_changed = enrollment and is_active is not None and enrollment[ 'is_active'] != is_active missing_attrs = [] if enrollment_attributes: actual_attrs = [ u"{namespace}:{name}".format(**attr) for attr in enrollment_attributes ] missing_attrs = set(REQUIRED_ATTRIBUTES.get( mode, [])) - set(actual_attrs) if has_api_key_permissions and (mode_changed or active_changed): if mode_changed and active_changed and not is_active: # if the requester wanted to deactivate but specified the wrong mode, fail # the request (on the assumption that the requester had outdated information # about the currently active enrollment). msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format( enrollment["mode"], mode) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) if missing_attrs: msg = u"Missing enrollment attributes: requested mode={} required attributes={}".format( mode, REQUIRED_ATTRIBUTES.get(mode)) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) response = api.update_enrollment( username, text_type(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes, # If we are updating enrollment by authorized api caller, we should allow expired modes include_expired=has_api_key_permissions) else: # Will reactivate inactive enrollments. response = api.add_enrollment( username, text_type(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes) cohort_name = request.data.get('cohort') if cohort_name is not None: cohort = get_cohort_by_name(course_id, cohort_name) try: add_user_to_cohort(cohort, user) except ValueError: # user already in cohort, probably because they were un-enrolled and re-enrolled log.exception('Cohort re-addition') email_opt_in = request.data.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) log.info( u'The user [%s] has already been enrolled in course run [%s].', username, course_id) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"The [{mode}] course mode is expired or otherwise unavailable for course run [{course_id}]." ).format(mode=mode, course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format( course_id=course_id) }) except CourseEnrollmentExistsError as error: log.warning( u'An enrollment already exists for user [%s] in course run [%s].', username, course_id) return Response(data=error.enrollment) except CourseEnrollmentError: log.exception( u"An error occurred while creating the new course enrollment for user " u"[%s] in course run [%s]", username, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": (u"An error occurred while creating the new course enrollment for user " u"'{username}' in course '{course_id}'").format( username=username, course_id=course_id) }) except CourseUserGroup.DoesNotExist: log.exception(u'Missing cohort [%s] in course run [%s]', cohort_name, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"An error occured while adding to cohort [%s]" % cohort_name }) finally: # Assumes that the ecommerce service uses an API key to authenticate. if has_api_key_permissions: current_enrollment = api.get_enrollment( username, text_type(course_id)) audit_log('enrollment_change_requested', course_id=text_type(course_id), requested_mode=mode, actual_mode=current_enrollment['mode'] if current_enrollment else None, requested_activation=is_active, actual_activation=current_enrollment['is_active'] if current_enrollment else None, user_id=user.id)
def test_request_global_staff_courses_with_claims(self): GlobalStaff().add_users(self.user) self._assert_role_using_claim('course_staff', 'staff_courses')
def test_request_global_staff_courses_using_scope(self): GlobalStaff().add_users(self.user) self._assert_role_using_scope('course_staff', 'staff_courses', assert_one_course=False)