Пример #1
0
 def test_update_creator_group(self):
     with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
         self.assertFalse(auth.has_access(self.user, CourseCreatorRole()))
         update_course_creator_group(self.admin, self.user, True)
         self.assertTrue(auth.has_access(self.user, CourseCreatorRole()))
         update_course_creator_group(self.admin, self.user, False)
         self.assertFalse(auth.has_access(self.user, CourseCreatorRole()))
Пример #2
0
    def test_creator_group_enabled_but_empty(self):
        """ Tests creator group feature on, but group empty. """
        with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
            self.assertFalse(has_access(self.user, CourseCreatorRole()))

            # Make user staff. This will cause CourseCreatorRole().has_user to return True.
            self.user.is_staff = True
            self.assertTrue(has_access(self.user, CourseCreatorRole()))
Пример #3
0
 def test_detail_post_no_json(self):
     resp = self.client.post(self.detail_url, data={"role": "staff"}, HTTP_ACCEPT="application/json")
     self.assertEqual(resp.status_code, 204)
     # reload user from DB
     ext_user = User.objects.get(email=self.ext_user.email)
     self.assertTrue(auth.has_access(ext_user, CourseStaffRole(self.course_locator)))
     self.assertFalse(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
     self.assert_enrolled()
Пример #4
0
 def test_detail_post(self):
     resp = self.client.post(self.detail_url, data={"role": None})
     self.assertEqual(resp.status_code, 204)
     # reload user from DB
     ext_user = User.objects.get(email=self.ext_user.email)
     # no content: should not be in any roles
     self.assertFalse(auth.has_access(ext_user, CourseStaffRole(self.course_locator)))
     self.assertFalse(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
     self.assert_not_enrolled()
Пример #5
0
    def test_creator_group_enabled_nonempty(self):
        """ Tests creator group feature on, user added. """
        with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
            add_users(self.admin, CourseCreatorRole(), self.user)
            self.assertTrue(has_access(self.user, CourseCreatorRole()))

            # check that a user who has not been added to the group still returns false
            user_not_added = User.objects.create_user('testuser2', '*****@*****.**', 'foo2')
            self.assertFalse(has_access(user_not_added, CourseCreatorRole()))

            # remove first user from the group and verify that CourseCreatorRole().has_user now returns false
            remove_users(self.admin, CourseCreatorRole(), self.user)
            self.assertFalse(has_access(self.user, CourseCreatorRole()))
Пример #6
0
    def test_add_granted(self):
        with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
            # Calling add_user_with_status_granted impacts is_user_in_course_group_role.
            self.assertFalse(auth.has_access(self.user, CourseCreatorRole()))

            add_user_with_status_granted(self.admin, self.user)
            self.assertEqual('granted', get_course_creator_status(self.user))

            # Calling add again will be a no-op (even if state is different).
            add_user_with_status_unrequested(self.user)
            self.assertEqual('granted', get_course_creator_status(self.user))

            self.assertTrue(auth.has_access(self.user, CourseCreatorRole()))
Пример #7
0
    def test_add_user_to_course_group(self):
        """
        Tests adding user to course group (happy path).
        """
        # Create groups for a new course (and assign instructor role to the creator).
        self.assertFalse(has_access(self.creator, CourseInstructorRole(self.course_key)))
        add_users(self.global_admin, CourseInstructorRole(self.course_key), self.creator)
        add_users(self.global_admin, CourseStaffRole(self.course_key), self.creator)
        self.assertTrue(has_access(self.creator, CourseInstructorRole(self.course_key)))

        # Add another user to the staff role.
        self.assertFalse(has_access(self.staff, CourseStaffRole(self.course_key)))
        add_users(self.creator, CourseStaffRole(self.course_key), self.staff)
        self.assertTrue(has_access(self.staff, CourseStaffRole(self.course_key)))
Пример #8
0
    def test_remove_user_from_course_group(self):
        """
        Tests removing user from course group (happy path).
        """
        add_users(self.global_admin, CourseInstructorRole(self.course_key), self.creator)
        add_users(self.global_admin, CourseStaffRole(self.course_key), self.creator)

        add_users(self.creator, CourseStaffRole(self.course_key), self.staff)
        self.assertTrue(has_access(self.staff, CourseStaffRole(self.course_key)))

        remove_users(self.creator, CourseStaffRole(self.course_key), self.staff)
        self.assertFalse(has_access(self.staff, CourseStaffRole(self.course_key)))

        remove_users(self.creator, CourseInstructorRole(self.course_key), self.creator)
        self.assertFalse(has_access(self.creator, CourseInstructorRole(self.course_key)))
Пример #9
0
    def test_course_creation_disabled(self):
        """ Tests that the COURSE_CREATION_DISABLED flag overrides course creator group settings. """
        with mock.patch.dict('django.conf.settings.FEATURES',
                             {'DISABLE_COURSE_CREATION': True, "ENABLE_CREATOR_GROUP": True}):
            # Add user to creator group.
            add_users(self.admin, CourseCreatorRole(), self.user)

            # DISABLE_COURSE_CREATION overrides (user is not marked as staff).
            self.assertFalse(has_access(self.user, CourseCreatorRole()))

            # Mark as staff. Now CourseCreatorRole().has_user returns true.
            self.user.is_staff = True
            self.assertTrue(has_access(self.user, CourseCreatorRole()))

            # Remove user from creator group. CourseCreatorRole().has_user still returns true because is_staff=True
            remove_users(self.admin, CourseCreatorRole(), self.user)
            self.assertTrue(has_access(self.user, CourseCreatorRole()))
Пример #10
0
    def test_detail_delete_instructor(self):
        auth.add_users(self.user, CourseInstructorRole(self.course_locator), self.ext_user, self.user)

        resp = self.client.delete(self.detail_url, HTTP_ACCEPT="application/json")
        self.assertEqual(resp.status_code, 204)
        # reload user from DB
        ext_user = User.objects.get(email=self.ext_user.email)
        self.assertFalse(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
Пример #11
0
 def test_add_user_not_active(self):
     """
     Tests that adding to creator group fails if user is not active
     """
     with mock.patch.dict('django.conf.settings.FEATURES',
                          {'DISABLE_COURSE_CREATION': False, "ENABLE_CREATOR_GROUP": True}):
         self.user.is_active = False
         add_users(self.admin, CourseCreatorRole(), self.user)
         self.assertFalse(has_access(self.user, CourseCreatorRole()))
Пример #12
0
def cm_course_delete(request, course_id = None):
    response_format = request.REQUEST.get('format','html')
    if response_format == 'json' or 'application/json' in request.META.get('HTTP/ACCEPT', 'application/json'):
        if request.method == 'POST':
            if validate_token(request.body, request) is False:
                log.warn("Unauthorized access made by course: %s, user: %s", request.json.get('email'))
                return HttpResponse('Unauthorized', status=401)

            email = request.json.get('email')
            try:
                instructor = User.objects.get(email = email)
                request.user = instructor
            except User.DoesNotExist:
                log.error("course deletion attempted by unregistered user: %s", email)
                return JsonResponse(json.dumps({ 'error': 'course deletion attempted by unregistered user' }), status = 400)

            if course_id is None:
                return JsonResponse(json.dumps({'error': 'Course ID does not exist'}), status = 404)

            try:
                course_key = CourseKey.from_string(course_id)
            except InvalidKeyError:
                try:
                    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
                except InvalidKeyError:
                    course_key = None

            if course_key is None:
                return JsonResponse(json.dumps({'error': 'Course Key not found for course id: ' + str(course_id)}), status = 404)

            if not auth.has_access(instructor, CourseInstructorRole(course_key)):
                log.error("course deletion attempted by unauthorized user: %s", email)
                return JsonResponse(json.dumps({'error': 'course deletion attempted by unauthorized user'}), status = 401)

            try:
                delete_course_and_groups(course_key, instructor.id)
                try:
                    StudentModule.objects.filter(course_id=course_key).delete()
                except:
                    # We need this for testing. The above mentioned model lives in
                    # LMS but our tests are executed via CMS. This piece of code
                    # makes sure that the test safely skips over this and runs
                    # successfully.
                    pass
                CourseEnrollment.objects.filter(course_id=course_key).delete()
                try:
                    CourseAccessRole.objects.get(course_id=course_key).delete()
                except CourseAccessRole.DoesNotExist:
                    pass
            except Exception as e:
                return JsonResponse(json.dumps({'error' : str(e)}), status = 500)

            return JsonResponse(json.dumps({'message': "successfully deleted course"}), status = 200)
        else:
            return JsonResponse(json.dumps({}), status=404)
    else:
        return JsonResponse(content=json.dumps({}), status=404)
Пример #13
0
 def test_add_user_not_authenticated(self):
     """
     Tests that adding to creator group fails if user is not authenticated
     """
     with mock.patch.dict('django.conf.settings.FEATURES',
                          {'DISABLE_COURSE_CREATION': False, "ENABLE_CREATOR_GROUP": True}):
         anonymous_user = AnonymousUser()
         role = CourseCreatorRole()
         add_users(self.admin, role, anonymous_user)
         self.assertFalse(has_access(anonymous_user, role))
Пример #14
0
    def test_detail_post_staff_other_inst(self):
        auth.add_users(self.user, CourseInstructorRole(self.course.id), self.user)

        resp = self.client.post(
            self.detail_url,
            data=json.dumps({"role": "staff"}),
            content_type="application/json",
            HTTP_ACCEPT="application/json",
        )
        self.assertEqual(resp.status_code, 204)
        # reload user from DB
        ext_user = User.objects.get(email=self.ext_user.email)
        self.assertTrue(auth.has_access(ext_user, CourseStaffRole(self.course.id)))
        self.assertFalse(auth.has_access(ext_user, CourseInstructorRole(self.course.id)))
        self.assert_enrolled()
        # check that other user is unchanged
        user = User.objects.get(email=self.user.email)
        self.assertTrue(auth.has_access(user, CourseInstructorRole(self.course.id)))
        self.assertFalse(CourseStaffRole(self.course.id).has_user(user))
Пример #15
0
    def test_post_last_instructor(self):
        auth.add_users(self.user, CourseInstructorRole(self.course_locator), self.ext_user)

        resp = self.client.post(self.detail_url, data={"role": "staff"}, HTTP_ACCEPT="application/json")
        self.assertEqual(resp.status_code, 400)
        result = json.loads(resp.content)
        self.assertIn("error", result)
        # reload user from DB
        ext_user = User.objects.get(email=self.ext_user.email)
        self.assertTrue(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
Пример #16
0
def is_mobile_available_for_user(user, descriptor):
    """
    Returns whether the given course is mobile_available for the given user.
    Checks:
        mobile_available flag on the course
        Beta User and staff access overrides the mobile_available flag
    Arguments:
        descriptor (CourseDescriptor|CourseOverview): course or overview of course in question
    """
    return (descriptor.mobile_available
            or auth.has_access(user, CourseBetaTesterRole(descriptor.id)) or
            _has_staff_access_to_descriptor(user, descriptor, descriptor.id))
Пример #17
0
def is_mobile_available_for_user(user, course):
    """
    Returns whether the given course is mobile_available for the given user.
    Checks:
        mobile_available flag on the course
        Beta User and staff access overrides the mobile_available flag
    """
    return (
        course.mobile_available or
        auth.has_access(user, CourseBetaTesterRole(course.id)) or
        _has_staff_access_to_descriptor(user, course, course.id)
    )
Пример #18
0
def is_mobile_available_for_user(user, course):
    """
    Returns whether the given course is mobile_available for the given user.
    Checks:
        mobile_available flag on the course
        Beta User and staff access overrides the mobile_available flag
    """
    return (
        course.mobile_available or
        auth.has_access(user, CourseBetaTesterRole(course.id)) or
        _has_staff_access_to_descriptor(user, course, course.id)
    )
Пример #19
0
def get_user_role(user, location, context=None):
    """
    Return corresponding string if user has staff or instructor role in Studio.
    This will not return student role because its purpose for using in Studio.

    :param location: a descriptor.location (which may be a Location or a CourseLocator)
    :param context: a course_id. This is not used if location is a CourseLocator.
    """
    if auth.has_access(user, CourseInstructorRole(location, context)):
        return 'instructor'
    else:
        return 'staff'
Пример #20
0
    def test_staff_cannot_delete_other(self):
        auth.add_users(self.user, CourseStaffRole(self.course.id), self.user, self.ext_user)
        self.user.is_staff = False
        self.user.save()

        resp = self.client.delete(self.detail_url)
        self.assertEqual(resp.status_code, 403)
        result = json.loads(resp.content)
        self.assertIn("error", result)
        # reload user from DB
        ext_user = User.objects.get(email=self.ext_user.email)
        self.assertTrue(auth.has_access(ext_user, CourseStaffRole(self.course.id)))
Пример #21
0
def get_user_role(user, location, context):
    """
    Return corresponding string if user has staff or instructor role in Studio.
    This will not return student role because its purpose for using in Studio.

    :param location: a descriptor.location
    :param context: a course_id
    """
    if auth.has_access(user, CourseInstructorRole(location, context)):
        return 'instructor'
    else:
        return 'staff'
Пример #22
0
 def can_load_mobile_no_enroll_check():
     """
     Can this enrolled user access this course from a mobile device?
     Note: does not check for enrollment since it is assumed the caller has done so.
     """
     return (
         # check start date
         can_load() and
         # check mobile_available flag
         (course.mobile_available
          or auth.has_access(user, CourseBetaTesterRole(course.id))
          or _has_staff_access_to_descriptor(user, course, course.id)))
Пример #23
0
    def test_staff_can_delete_self(self):
        auth.add_users(self.user, CourseStaffRole(self.course.id), self.user)
        self.user.is_staff = False
        self.user.save()

        self_url = self.course_team_url(email=self.user.email)

        resp = self.client.delete(self_url)
        self.assertEqual(resp.status_code, 204)
        # reload user from DB
        user = User.objects.get(email=self.user.email)
        self.assertFalse(auth.has_access(user, CourseStaffRole(self.course.id)))
Пример #24
0
    def test_staff_can_delete_self(self):
        auth.add_users(self.user, CourseStaffRole(self.course.id), self.user)
        self.user.is_staff = False
        self.user.save()

        self_url = self.course_team_url(email=self.user.email)

        resp = self.client.delete(self_url)
        self.assertEqual(resp.status_code, 204)
        # reload user from DB
        user = User.objects.get(email=self.user.email)
        self.assertFalse(auth.has_access(user,
                                         CourseStaffRole(self.course.id)))
Пример #25
0
 def test_detail_post_instructor(self):
     resp = self.client.post(
         self.detail_url,
         data=json.dumps({"role": "instructor"}),
         content_type="application/json",
         HTTP_ACCEPT="application/json",
     )
     self.assertEqual(resp.status_code, 204)
     # reload user from DB
     ext_user = User.objects.get(email=self.ext_user.email)
     self.assertTrue(auth.has_access(ext_user, CourseInstructorRole(self.course_locator)))
     self.assertFalse(CourseStaffRole(self.course_locator).has_user(ext_user))
     self.assert_enrolled()
Пример #26
0
    def test_remove_user_from_course_group(self):
        """
        Tests removing user from course group (happy path).
        """
        add_users(self.global_admin, CourseInstructorRole(self.course_key),
                  self.creator)
        add_users(self.global_admin, CourseStaffRole(self.course_key),
                  self.creator)

        add_users(self.creator, CourseStaffRole(self.course_key), self.staff)
        self.assertTrue(
            has_access(self.staff, CourseStaffRole(self.course_key)))

        remove_users(self.creator, CourseStaffRole(self.course_key),
                     self.staff)
        self.assertFalse(
            has_access(self.staff, CourseStaffRole(self.course_key)))

        remove_users(self.creator, CourseInstructorRole(self.course_key),
                     self.creator)
        self.assertFalse(
            has_access(self.creator, CourseInstructorRole(self.course_key)))
Пример #27
0
def has_course_access(user, course_key, role=CourseStaffRole):
    """
    Return True if user allowed to access this course_id
    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
    """
    if GlobalStaff().has_user(user):
        return True
    return auth.has_access(user, role(course_key))
Пример #28
0
    def test_detail_delete_staff(self):
        auth.add_users(self.user, CourseStaffRole(self.course_locator),
                       self.ext_user)

        resp = self.client.delete(
            self.detail_url,
            HTTP_ACCEPT="application/json",
        )
        self.assertEqual(resp.status_code, 204)
        # reload user from DB
        ext_user = User.objects.get(email=self.ext_user.email)
        self.assertFalse(
            auth.has_access(ext_user, CourseStaffRole(self.course_locator)))
Пример #29
0
 def test_detail_post_instructor(self):
     resp = self.client.post(
         self.detail_url,
         data=json.dumps({"role": "instructor"}),
         content_type="application/json",
         HTTP_ACCEPT="application/json",
     )
     self.assertEqual(resp.status_code, 204)
     # reload user from DB
     ext_user = User.objects.get(email=self.ext_user.email)
     self.assertTrue(auth.has_access(ext_user, CourseInstructorRole(self.course.id)))
     self.assertFalse(CourseStaffRole(self.course.id).has_user(ext_user))
     self.assert_enrolled()
Пример #30
0
    def test_delete_last_instructor(self):
        auth.add_users(self.user, CourseInstructorRole(self.course.id), self.ext_user)

        resp = self.client.delete(
            self.detail_url,
            HTTP_ACCEPT="application/json",
        )
        self.assertEqual(resp.status_code, 400)
        result = json.loads(resp.content)
        self.assertIn("error", result)
        # reload user from DB
        ext_user = User.objects.get(email=self.ext_user.email)
        self.assertTrue(auth.has_access(ext_user, CourseInstructorRole(self.course.id)))
Пример #31
0
def is_mobile_available_for_user(user, descriptor):
    """
    Returns whether the given course is mobile_available for the given user.
    Checks:
        mobile_available flag on the course
        Beta User and staff access overrides the mobile_available flag
    Arguments:
        descriptor (CourseDescriptor|CourseOverview): course or overview of course in question
    """
    return (
        descriptor.mobile_available or
        auth.has_access(user, CourseBetaTesterRole(descriptor.id)) or
        _has_staff_access_to_descriptor(user, descriptor, descriptor.id)
    )
Пример #32
0
    def test_staff_cannot_delete_other(self):
        auth.add_users(self.user, CourseStaffRole(self.course_locator),
                       self.user, self.ext_user)
        self.user.is_staff = False
        self.user.save()

        resp = self.client.delete(self.detail_url)
        self.assertEqual(resp.status_code, 400)
        result = json.loads(resp.content)
        self.assertIn("error", result)
        # reload user from DB
        ext_user = User.objects.get(email=self.ext_user.email)
        self.assertTrue(
            auth.has_access(ext_user, CourseStaffRole(self.course_locator)))
Пример #33
0
def _create_library(request):
    """
    Helper method for creating a new library.
    """
    if not auth.has_access(request.user, CourseCreatorRole()):
        log.exception(u"User %s tried to create a library without permission", request.user.username)
        raise PermissionDenied()
    display_name = None
    try:
        display_name = request.json['display_name']
        org = request.json['org']
        library = request.json.get('number', None)
        if library is None:
            library = request.json['library']
        store = modulestore()
        with store.default_store(ModuleStoreEnum.Type.split):
            new_lib = store.create_library(
                org=org,
                library=library,
                user_id=request.user.id,
                fields={"display_name": display_name},
            )
        # Give the user admin ("Instructor") role for this library:
        add_instructor(new_lib.location.library_key, request.user, request.user)
    except KeyError as error:
        log.exception("Unable to create library - missing required JSON key.")
        return JsonResponseBadRequest({
            "ErrMsg": _("Unable to create library - missing required field '{field}'".format(field=error.message))
        })
    except InvalidKeyError as error:
        log.exception("Unable to create library - invalid key.")
        return JsonResponseBadRequest({
            "ErrMsg": _("Unable to create library '{name}'.\n\n{err}").format(name=display_name, err=error.message)
        })
    except DuplicateCourseError:
        log.exception("Unable to create library - one already exists with the same key.")
        return JsonResponseBadRequest({
            'ErrMsg': _(
                'There is already a library defined with the same '
                'organization and library code. Please '
                'change your library code so that it is unique within your organization.'
            )
        })

    lib_key_str = unicode(new_lib.location.library_key)
    return JsonResponse({
        'url': reverse_library_url('library_handler', lib_key_str),
        'library_key': lib_key_str,
    })
Пример #34
0
def _create_or_rerun_course(request):
    """
    To be called by requests that create a new destination course (i.e., create_new_course and rerun_course)
    Returns the destination course_key and overriding fields for the new course.
    Raises InvalidLocationError and InvalidKeyError
    """
    if not auth.has_access(request.user, CourseCreatorRole()):
        raise PermissionDenied()

    try:
        org = request.json.get('org')
        number = request.json.get('number')
        display_name = request.json.get('display_name')
        run = request.json.get('run')

        # allow/disable unicode characters in course_id according to settings
        if not settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID'):
            if _has_non_ascii_characters(org) or _has_non_ascii_characters(number) or _has_non_ascii_characters(run):
                return JsonResponse(
                    {'error': _('Special characters not allowed in organization, course number, and course run.')},
                    status=400
                )

        course_key = SlashSeparatedCourseKey(org, number, run)
        fields = {'display_name': display_name} if display_name is not None else {}

        if 'source_course_key' in request.json:
            return _rerun_course(request, course_key, fields)
        else:
            return _create_new_course(request, course_key, fields)

    except InvalidLocationError:
        return JsonResponse({
            'ErrMsg': _(
                'There is already a course defined with the same '
                'organization, course number, and course run. Please '
                'change either organization or course number to be unique.'
            ),
            'OrgErrMsg': _(
                'Please change either the organization or '
                'course number so that it is unique.'),
            'CourseErrMsg': _(
                'Please change either the organization or '
                'course number so that it is unique.'),
        })
    except InvalidKeyError as error:
        return JsonResponse({
            "ErrMsg": _("Unable to create course '{name}'.\n\n{err}").format(name=display_name, err=error.message)}
        )
Пример #35
0
    def test_detail_post_staff_other_inst(self):
        auth.add_users(self.user, CourseInstructorRole(self.course_locator),
                       self.user)

        resp = self.client.post(
            self.detail_url,
            data=json.dumps({"role": "staff"}),
            content_type="application/json",
            HTTP_ACCEPT="application/json",
        )
        self.assertEqual(resp.status_code, 204)
        # reload user from DB
        ext_user = User.objects.get(email=self.ext_user.email)
        self.assertTrue(
            auth.has_access(ext_user, CourseStaffRole(self.course_locator)))
        self.assertFalse(
            auth.has_access(ext_user,
                            CourseInstructorRole(self.course_locator)))
        self.assert_enrolled()
        # check that other user is unchanged
        user = User.objects.get(email=self.user.email)
        self.assertTrue(
            auth.has_access(user, CourseInstructorRole(self.course_locator)))
        self.assertFalse(CourseStaffRole(self.course_locator).has_user(user))
Пример #36
0
def get_user_role(user, course_id):
    """
    What type of access: staff or instructor does this user have in Studio?

    No code should use this for access control, only to quickly serialize the type of access
    where this code knows that Instructor trumps Staff and assumes the user has one or the other.

    This will not return student role because its purpose for using in Studio.

    :param course_id: the course_id of the course we're interested in
    """
    # afaik, this is only used in lti
    if auth.has_access(user, CourseInstructorRole(course_id)):
        return 'instructor'
    else:
        return 'staff'
Пример #37
0
def has_course_access(user, location, role=CourseStaffRole):
    """
    Return True if user allowed to access this piece of data
    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
    """
    if GlobalStaff().has_user(user):
        return True
    if not isinstance(location, CourseLocator):
        # this can be expensive if location is not category=='course'
        location = get_course_location_for_item(location)
    return auth.has_access(user, role(location))
Пример #38
0
def mobile_course_enrollments(enrollments, user):
    """
    Return enrollments only if courses are mobile_available (or if the user has
    privileged (beta, staff, instructor) access)

    :param enrollments is a list of CourseEnrollments.
    """
    for enr in enrollments:
        course = enr.course

        # Implicitly includes instructor role via the following has_access check
        role = CourseBetaTesterRole(course.id)

        # The course doesn't always really exist -- we can have bad data in the enrollments
        # pointing to non-existent (or removed) courses, in which case `course` is None.
        if course and (course.mobile_available or auth.has_access(user, role) or access.has_access(user, 'staff', course)):
            yield enr
Пример #39
0
        def change_state_and_verify_email(state, is_creator):
            """ Changes user state, verifies creator status, and verifies e-mail is sent based on transition """
            self._change_state(state)
            self.assertEqual(is_creator, auth.has_access(self.user, CourseCreatorRole()))

            context = {'studio_request_email': self.studio_request_email}
            if state == CourseCreator.GRANTED:
                template = 'emails/course_creator_granted.txt'
            elif state == CourseCreator.DENIED:
                template = 'emails/course_creator_denied.txt'
            else:
                template = 'emails/course_creator_revoked.txt'
            email_user.assert_called_with(
                mock_render_to_string('emails/course_creator_subject.txt', context),
                mock_render_to_string(template, context),
                self.studio_request_email
            )
Пример #40
0
def mobile_course_enrollments(enrollments, user):
    """
    Return enrollments only if courses are mobile_available (or if the user has
    privileged (beta, staff, instructor) access)

    :param enrollments is a list of CourseEnrollments.
    """
    for enr in enrollments:
        course = enr.course

        # Implicitly includes instructor role via the following has_access check
        role = CourseBetaTesterRole(course.id)

        # The course doesn't always really exist -- we can have bad data in the enrollments
        # pointing to non-existent (or removed) courses, in which case `course` is None.
        if course and (course.mobile_available or auth.has_access(user, role)
                       or access.has_access(user, 'staff', course)):
            yield enr
Пример #41
0
        def change_state_and_verify_email(state, is_creator):
            """ Changes user state, verifies creator status, and verifies e-mail is sent based on transition """
            self._change_state(state)
            self.assertEqual(is_creator,
                             auth.has_access(self.user, CourseCreatorRole()))

            context = {'studio_request_email': self.studio_request_email}
            if state == CourseCreator.GRANTED:
                template = 'emails/course_creator_granted.txt'
            elif state == CourseCreator.DENIED:
                template = 'emails/course_creator_denied.txt'
            else:
                template = 'emails/course_creator_revoked.txt'
            email_user.assert_called_with(
                mock_render_to_string('emails/course_creator_subject.txt',
                                      context),
                mock_render_to_string(template, context),
                self.studio_request_email)
Пример #42
0
def mobile_available_when_enrolled(course, user):
    """
    Determines whether a user has access to a course in a mobile context.
    Checks if the course is marked as mobile_available or the user has extra permissions
    that gives them access anyway.
    Does not check if the user is actually enrolled in the course
    """

    # The course doesn't always really exist -- we can have bad data in the enrollments
    # pointing to non-existent (or removed) courses, in which case `course` is None.
    if not course:
        return None

    # Implicitly includes instructor role via the following has_access check
    beta_tester_role = CourseBetaTesterRole(course.id)

    return (course.mobile_available or auth.has_access(user, beta_tester_role)
            or access.has_access(user, 'staff', course))
Пример #43
0
def has_course_access(user, course_key, role=CourseStaffRole):
    """
    Return True if user allowed to access this course_id
    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
    """
    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 auth.has_access(user, role(course_key.for_branch(None)))
Пример #44
0
    def test_change_status(self, email_user):
        """
        Tests that updates to state impact the creator group maintained in authz.py and that e-mails are sent.
        """
        def change_state_and_verify_email(state, is_creator):
            """ Changes user state, verifies creator status, and verifies e-mail is sent based on transition """
            self._change_state(state)
            self.assertEqual(is_creator,
                             auth.has_access(self.user, CourseCreatorRole()))

            context = {'studio_request_email': self.studio_request_email}
            if state == CourseCreator.GRANTED:
                template = 'emails/course_creator_granted.txt'
            elif state == CourseCreator.DENIED:
                template = 'emails/course_creator_denied.txt'
            else:
                template = 'emails/course_creator_revoked.txt'
            email_user.assert_called_with(
                mock_render_to_string('emails/course_creator_subject.txt',
                                      context),
                mock_render_to_string(template, context),
                self.studio_request_email)

        with mock.patch.dict('django.conf.settings.FEATURES',
                             self.enable_creator_group_patch):

            # User is initially unrequested.
            self.assertFalse(auth.has_access(self.user, CourseCreatorRole()))

            change_state_and_verify_email(CourseCreator.GRANTED, True)

            change_state_and_verify_email(CourseCreator.DENIED, False)

            change_state_and_verify_email(CourseCreator.GRANTED, True)

            change_state_and_verify_email(CourseCreator.PENDING, False)

            change_state_and_verify_email(CourseCreator.GRANTED, True)

            change_state_and_verify_email(CourseCreator.UNREQUESTED, False)

            change_state_and_verify_email(CourseCreator.DENIED, False)
Пример #45
0
 def test_creator_group_not_enabled(self):
     """
     Tests that CourseCreatorRole().has_user always returns True if ENABLE_CREATOR_GROUP
     and DISABLE_COURSE_CREATION are both not turned on.
     """
     self.assertTrue(has_access(self.user, CourseCreatorRole()))
Пример #46
0
def create_new_course(request):
    """
    Create a new course.

    Returns the URL for the course overview page.
    """
    if not auth.has_access(request.user, CourseCreatorRole()):
        raise PermissionDenied()

    org = request.json.get('org')
    number = request.json.get('number')
    display_name = request.json.get('display_name')
    run = request.json.get('run')

    # allow/disable unicode characters in course_id according to settings
    if not settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID'):
        if _has_non_ascii_characters(org) or _has_non_ascii_characters(
                number) or _has_non_ascii_characters(run):
            return JsonResponse(
                {
                    'error':
                    _('Special characters not allowed in organization, course number, and course run.'
                      )
                },
                status=400)

    try:
        dest_location = Location(u'i4x', org, number, u'course', run)
    except InvalidLocationError as error:
        return JsonResponse({
            "ErrMsg":
            _("Unable to create course '{name}'.\n\n{err}").format(
                name=display_name, err=error.message)
        })

    # see if the course already exists
    existing_course = None
    try:
        existing_course = modulestore('direct').get_item(dest_location)
    except ItemNotFoundError:
        pass
    if existing_course is not None:
        return JsonResponse({
            'ErrMsg':
            _('There is already a course defined with the same '
              'organization, course number, and course run. Please '
              'change either organization or course number to be '
              'unique.'),
            'OrgErrMsg':
            _('Please change either the organization or '
              'course number so that it is unique.'),
            'CourseErrMsg':
            _('Please change either the organization or '
              'course number so that it is unique.'),
        })

    # dhm: this query breaks the abstraction, but I'll fix it when I do my suspended refactoring of this
    # file for new locators. get_items should accept a query rather than requiring it be a legal location
    course_search_location = bson.son.SON({
        '_id.tag':
        'i4x',
        # cannot pass regex to Location constructor; thus this hack
        # pylint: disable=E1101
        '_id.org':
        re.compile(u'^{}$'.format(dest_location.org),
                   re.IGNORECASE | re.UNICODE),
        # pylint: disable=E1101
        '_id.course':
        re.compile(u'^{}$'.format(dest_location.course),
                   re.IGNORECASE | re.UNICODE),
        '_id.category':
        'course',
    })
    courses = modulestore().collection.find(course_search_location,
                                            fields=('_id'))
    if courses.count() > 0:
        return JsonResponse({
            'ErrMsg':
            _('There is already a course defined with the same '
              'organization and course number. Please '
              'change at least one field to be unique.'),
            'OrgErrMsg':
            _('Please change either the organization or '
              'course number so that it is unique.'),
            'CourseErrMsg':
            _('Please change either the organization or '
              'course number so that it is unique.'),
        })

    # instantiate the CourseDescriptor and then persist it
    # note: no system to pass
    if display_name is None:
        metadata = {}
    else:
        metadata = {'display_name': display_name}

    # Set a unique wiki_slug for newly created courses. To maintain active wiki_slugs for existing xml courses this
    # cannot be changed in CourseDescriptor.
    wiki_slug = "{0}.{1}.{2}".format(dest_location.org, dest_location.course,
                                     dest_location.name)
    definition_data = {'wiki_slug': wiki_slug}

    modulestore('direct').create_and_save_xmodule(
        dest_location, definition_data=definition_data, metadata=metadata)
    new_course = modulestore('direct').get_item(dest_location)

    # clone a default 'about' overview module as well
    dest_about_location = dest_location.replace(category='about',
                                                name='overview')
    overview_template = AboutDescriptor.get_template('overview.yaml')
    modulestore('direct').create_and_save_xmodule(
        dest_about_location,
        system=new_course.system,
        definition_data=overview_template.get('data'))

    initialize_course_tabs(new_course, request.user)

    new_location = loc_mapper().translate_location(
        new_course.location.course_id, new_course.location, False, True)
    # can't use auth.add_users here b/c it requires request.user to already have Instructor perms in this course
    # however, we can assume that b/c this user had authority to create the course, the user can add themselves
    CourseInstructorRole(new_location).add_users(request.user)
    auth.add_users(request.user, CourseStaffRole(new_location), request.user)

    # seed the forums
    seed_permissions_roles(new_course.location.course_id)

    # auto-enroll the course creator in the course so that "View Live" will
    # work.
    CourseEnrollment.enroll(request.user, new_course.location.course_id)
    _users_assign_default_role(new_course.location)

    return JsonResponse({'url': new_location.url_reverse("course/", "")})
Пример #47
0
def create_new_course(request):
    """
    Create a new course.

    Returns the URL for the course overview page.
    """
    if not auth.has_access(request.user, CourseCreatorRole()):
        raise PermissionDenied()

    org = request.json.get('org')
    number = request.json.get('number')
    display_name = request.json.get('display_name')
    run = request.json.get('run')

    # allow/disable unicode characters in course_id according to settings
    if not settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID'):
        if _has_non_ascii_characters(org) or _has_non_ascii_characters(
                number) or _has_non_ascii_characters(run):
            return JsonResponse(
                {
                    'error':
                    _('Special characters not allowed in organization, course number, and course run.'
                      )
                },
                status=400)

    try:
        course_key = SlashSeparatedCourseKey(org, number, run)

        # instantiate the CourseDescriptor and then persist it
        # note: no system to pass
        if display_name is None:
            metadata = {}
        else:
            metadata = {'display_name': display_name}

        # Set a unique wiki_slug for newly created courses. To maintain active wiki_slugs for
        # existing xml courses this cannot be changed in CourseDescriptor.
        # # TODO get rid of defining wiki slug in this org/course/run specific way and reconcile
        # w/ xmodule.course_module.CourseDescriptor.__init__
        wiki_slug = u"{0}.{1}.{2}".format(course_key.org, course_key.course,
                                          course_key.run)
        definition_data = {'wiki_slug': wiki_slug}

        # Create the course then fetch it from the modulestore
        # Check if role permissions group for a course named like this already exists
        # Important because role groups are case insensitive
        if CourseRole.course_group_already_exists(course_key):
            raise InvalidLocationError()

        fields = {}
        fields.update(definition_data)
        fields.update(metadata)

        # Creating the course raises InvalidLocationError if an existing course with this org/name is found
        new_course = modulestore('direct').create_course(
            course_key.org,
            course_key.offering,
            fields=fields,
        )

        # can't use auth.add_users here b/c it requires request.user to already have Instructor perms in this course
        # however, we can assume that b/c this user had authority to create the course, the user can add themselves
        CourseInstructorRole(new_course.id).add_users(request.user)
        auth.add_users(request.user, CourseStaffRole(new_course.id),
                       request.user)

        # seed the forums
        seed_permissions_roles(new_course.id)

        # auto-enroll the course creator in the course so that "View Live" will
        # work.
        CourseEnrollment.enroll(request.user, new_course.id)
        _users_assign_default_role(new_course.id)

        return JsonResponse(
            {'url': reverse_course_url('course_handler', new_course.id)})

    except InvalidLocationError:
        return JsonResponse({
            'ErrMsg':
            _('There is already a course defined with the same '
              'organization, course number, and course run. Please '
              'change either organization or course number to be unique.'),
            'OrgErrMsg':
            _('Please change either the organization or '
              'course number so that it is unique.'),
            'CourseErrMsg':
            _('Please change either the organization or '
              'course number so that it is unique.'),
        })
    except InvalidKeyError as error:
        return JsonResponse({
            "ErrMsg":
            _("Unable to create course '{name}'.\n\n{err}").format(
                name=display_name, err=error.message)
        })