示例#1
0
 def test_get_user_role_staff(self):
     """
     Verifies if user is staff.
     """
     add_users(self.global_admin, CourseStaffRole(self.course_key),
               self.staff)
     self.assertEqual('staff', get_user_role(self.staff, self.course_key))
示例#2
0
    def test_add_user_to_group_requires_staff_access(self):
        with self.assertRaises(PermissionDenied):
            self.admin.is_staff = False
            add_users(self.admin, CourseCreatorRole(), self.user)

        with self.assertRaises(PermissionDenied):
            add_users(self.user, CourseCreatorRole(), self.user)
示例#3
0
def add_instructor(course_key, requesting_user, new_instructor):
    """
    Adds given user as instructor and staff to the given course,
    after verifying that the requesting_user has permission to do so.
    """
    # can't use auth.add_users here b/c it requires user to already have Instructor perms in this course
    CourseInstructorRole(course_key).add_users(new_instructor)
    auth.add_users(requesting_user, CourseStaffRole(course_key), new_instructor)
示例#4
0
 def test_add_user_to_group_requires_authenticated(self):
     with self.assertRaises(PermissionDenied):
         with mock.patch(
             'django.contrib.auth.models.User.is_authenticated',
             new_callable=mock.PropertyMock
         ) as mock_is_auth:
             mock_is_auth.return_value = False
             add_users(self.admin, CourseCreatorRole(), self.user)
    def test_studio_user_permissions(self):
        """
        Test that user could attach to the problem only libraries that he has access (or which were created by him).
        This test was created on the basis of bug described in the pull requests on github:
        https://github.com/edx/edx-platform/pull/11331
        https://github.com/edx/edx-platform/pull/11611
        """
        self._create_library(org='admin_org_1', library='lib_adm_1', display_name='admin_lib_1')
        self._create_library(org='admin_org_2', library='lib_adm_2', display_name='admin_lib_2')

        self._login_as_non_staff_user()

        self._create_library(org='staff_org_1', library='lib_staff_1', display_name='staff_lib_1')
        self._create_library(org='staff_org_2', library='lib_staff_2', display_name='staff_lib_2')

        with modulestore().default_store(ModuleStoreEnum.Type.split):
            course = CourseFactory.create()

        instructor_role = CourseInstructorRole(course.id)
        auth.add_users(self.user, instructor_role, self.non_staff_user)

        lib_block = ItemFactory.create(
            category='library_content',
            parent_location=course.location,
            user_id=self.non_staff_user.id,
            publish_item=False
        )

        def _get_settings_html():
            """
            Helper function to get block settings HTML
            Used to check the available libraries.
            """
            edit_view_url = reverse_usage_url("xblock_view_handler", lib_block.location, {"view_name": STUDIO_VIEW})

            resp = self.client.get_json(edit_view_url)
            self.assertEqual(resp.status_code, 200)

            return parse_json(resp)['html']

        self._login_as_staff_user()
        staff_settings_html = _get_settings_html()
        self.assertIn('staff_lib_1', staff_settings_html)
        self.assertIn('staff_lib_2', staff_settings_html)
        self.assertIn('admin_lib_1', staff_settings_html)
        self.assertIn('admin_lib_2', staff_settings_html)

        self._login_as_non_staff_user()
        response = self.client.get_json(LIBRARY_REST_URL)
        staff_libs = parse_json(response)
        self.assertEqual(2, len(staff_libs))

        non_staff_settings_html = _get_settings_html()
        self.assertIn('staff_lib_1', non_staff_settings_html)
        self.assertIn('staff_lib_2', non_staff_settings_html)
        self.assertNotIn('admin_lib_1', non_staff_settings_html)
        self.assertNotIn('admin_lib_2', non_staff_settings_html)
示例#6
0
 def setUp(self):
     """
     Set up test variables
     """
     super(CCXCourseGroupTest, self).setUp()
     self.global_admin = AdminFactory()
     self.staff = User.objects.create_user('teststaff', '*****@*****.**', 'foo')
     self.ccx_course_key = CCXLocator.from_string('ccx-v1:edX+DemoX+Demo_Course+ccx@1')
     add_users(self.global_admin, CourseStaffRole(self.ccx_course_key), self.staff)
示例#7
0
 def test_remove_user_from_course_group_permission_denied(self):
     """
     Verifies PermissionDenied if caller of remove_user_from_course_group is not instructor role.
     """
     add_users(self.global_admin, CourseInstructorRole(self.course_key), self.creator)
     another_staff = User.objects.create_user('another', '*****@*****.**', 'foo')
     add_users(self.global_admin, CourseStaffRole(self.course_key), self.creator, self.staff, another_staff)
     with self.assertRaises(PermissionDenied):
         remove_users(self.staff, CourseStaffRole(self.course_key), another_staff)
示例#8
0
def update_course_creator_group(caller, user, add):
    """
    Method for adding and removing users from the creator group.

    Caller must have staff permissions.
    """
    if add:
        auth.add_users(caller, CourseCreatorRole(), user)
    else:
        auth.remove_users(caller, CourseCreatorRole(), user)
示例#9
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)
         assert not user_has_role(self.user, CourseCreatorRole())
示例#10
0
    def test_detail_delete_instructor(self):
        auth.add_users(self.user, CourseInstructorRole(self.course.id), 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.user_has_role(ext_user, CourseInstructorRole(self.course.id)))
示例#11
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.user_has_role(user, CourseStaffRole(self.course.id)))
示例#12
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)
         assert not user_has_role(anonymous_user, role)
示例#13
0
 def test_get_user_role_instructor(self):
     """
     Verifies if user is instructor.
     """
     add_users(self.global_admin, CourseInstructorRole(self.course_key),
               self.instructor)
     self.assertEqual('instructor',
                      get_user_role(self.instructor, self.course_key))
     add_users(self.global_admin, CourseStaffRole(self.course_key),
               self.staff)
     self.assertEqual('instructor',
                      get_user_role(self.instructor, self.course_key))
示例#14
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.decode('utf-8'))
        self.assertIn("error", result)
        # reload user from DB
        ext_user = User.objects.get(email=self.ext_user.email)
        self.assertTrue(auth.user_has_role(ext_user, CourseStaffRole(self.course.id)))
示例#15
0
    def test_permission_denied_other(self):
        auth.add_users(self.user, CourseStaffRole(self.course.id), self.user)
        self.user.is_staff = False
        self.user.save()

        resp = self.client.post(
            self.detail_url,
            data={"role": "instructor"},
            HTTP_ACCEPT="application/json",
        )
        self.assertEqual(resp.status_code, 403)
        result = json.loads(resp.content.decode('utf-8'))
        self.assertIn("error", result)
示例#16
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.decode('utf-8'))
        self.assertIn("error", result)
        # reload user from DB
        ext_user = User.objects.get(email=self.ext_user.email)
        self.assertTrue(auth.user_has_role(ext_user, CourseInstructorRole(self.course.id)))
示例#17
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(user_has_role(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(user_has_role(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(user_has_role(self.user, CourseCreatorRole()))
示例#18
0
 def setUp(self):
     """
     Set up test variables
     """
     super().setUp()
     self.global_admin = AdminFactory()
     self.staff = UserFactory.create(username='******',
                                     email='*****@*****.**',
                                     password='******')
     self.ccx_course_key = CCXLocator.from_string(
         'ccx-v1:edX+DemoX+Demo_Course+ccx@1')
     add_users(self.global_admin, CourseStaffRole(self.ccx_course_key),
               self.staff)
示例#19
0
    def test_filter_by_roles_course_staff(self):
        """
        Verify that course_ids are filtered by the provided roles.
        """
        # Make this user a course staff user for the course.
        course_staff_user = self.create_user(username='******', is_staff=False)
        add_users(self.global_admin, CourseStaffRole(self.course.id), course_staff_user)

        # Create a second course, along with an instructor user for it.
        alternate_course1 = self.create_course(org='test1')
        course_instructor_user = self.create_user(username='******', is_staff=False)
        add_users(self.global_admin, CourseInstructorRole(alternate_course1.id), course_instructor_user)

        # Create a third course, along with an user that has both staff and instructor for it.
        alternate_course2 = self.create_course(org='test2')
        course_instructor_staff_user = self.create_user(username='******', is_staff=False)
        add_users(self.global_admin, CourseInstructorRole(alternate_course2.id), course_instructor_staff_user)
        add_users(self.global_admin, CourseStaffRole(alternate_course2.id), course_instructor_staff_user)

        # Requesting the courses for which the course staff user is staff should return *only* the single course.
        self.setup_user(self.staff_user)
        filtered_response = self.verify_response(params={
            'username': course_staff_user.username,
            'role': 'staff'
        })
        assert len(filtered_response.data['results']) == 1
        assert filtered_response.data['results'][0].startswith(self.course.org)

        # The course staff user does *not* have the course instructor role on any courses.
        filtered_response = self.verify_response(params={
            'username': course_staff_user.username,
            'role': 'instructor'
        })
        assert len(filtered_response.data['results']) == 0

        # The course instructor user only has the course instructor role on one course.
        filtered_response = self.verify_response(params={
            'username': course_instructor_user.username,
            'role': 'instructor'
        })
        assert len(filtered_response.data['results']) == 1
        assert filtered_response.data['results'][0].startswith(alternate_course1.org)

        # The course instructor user has the inferred course staff role on one course.
        self.setup_user(course_instructor_user)
        filtered_response = self.verify_response(params={
            'username': course_instructor_user.username,
            'role': 'staff'
        })
        assert len(filtered_response.data['results']) == 1
        assert filtered_response.data['results'][0].startswith(alternate_course1.org)

        # The user with both instructor AND staff on a course has the inferred course staff role on that one course.
        self.setup_user(course_instructor_staff_user)
        filtered_response = self.verify_response(params={
            'username': course_instructor_staff_user.username,
            'role': 'staff'
        })
        assert len(filtered_response.data['results']) == 1
        assert filtered_response.data['results'][0].startswith(alternate_course2.org)
示例#20
0
 def test_remove_user_from_course_group_permission_denied(self):
     """
     Verifies PermissionDenied if caller of remove_user_from_course_group is not instructor role.
     """
     add_users(self.global_admin, CourseInstructorRole(self.course_key),
               self.creator)
     another_staff = UserFactory.create(
         username='******',
         email='*****@*****.**',
         password='******',
     )
     add_users(self.global_admin, CourseStaffRole(self.course_key),
               self.creator, self.staff, another_staff)
     with pytest.raises(PermissionDenied):
         remove_users(self.staff, CourseStaffRole(self.course_key),
                      another_staff)
示例#21
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(user_has_role(self.user, CourseCreatorRole()))

            # Mark as staff. Now CourseCreatorRole().has_user returns true.
            self.user.is_staff = True
            self.assertTrue(user_has_role(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(user_has_role(self.user, CourseCreatorRole()))
示例#22
0
    def test_import_in_existing_course(self):
        """
        Check that course is imported successfully in existing course and users have their access roles
        """
        # Create a non_staff user and add it to course staff only
        __, nonstaff_user = self.create_non_staff_authed_user_client()
        auth.add_users(self.user, CourseStaffRole(self.course.id),
                       nonstaff_user)

        course = self.store.get_course(self.course.id)
        self.assertIsNotNone(course)
        display_name_before_import = course.display_name

        # Check that global staff user can import course
        with open(self.good_tar, 'rb') as gtar:  # lint-amnesty, pylint: disable=bad-option-value, open-builtin
            args = {"name": self.good_tar, "course-data": [gtar]}
            resp = self.client.post(self.url, args)
        self.assertEqual(resp.status_code, 200)

        course = self.store.get_course(self.course.id)
        self.assertIsNotNone(course)
        display_name_after_import = course.display_name

        # Check that course display name have changed after import
        self.assertNotEqual(display_name_before_import,
                            display_name_after_import)

        # Now check that non_staff user has his same role
        self.assertFalse(
            CourseInstructorRole(self.course.id).has_user(nonstaff_user))
        self.assertTrue(
            CourseStaffRole(self.course.id).has_user(nonstaff_user))

        # Now course staff user can also successfully import course
        self.client.login(username=nonstaff_user.username, password='******')
        with open(self.good_tar, 'rb') as gtar:  # lint-amnesty, pylint: disable=bad-option-value, open-builtin
            args = {"name": self.good_tar, "course-data": [gtar]}
            resp = self.client.post(self.url, args)
        self.assertEqual(resp.status_code, 200)

        # Now check that non_staff user has his same role
        self.assertFalse(
            CourseInstructorRole(self.course.id).has_user(nonstaff_user))
        self.assertTrue(
            CourseStaffRole(self.course.id).has_user(nonstaff_user))
示例#23
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.user_has_role(ext_user, CourseStaffRole(self.course.id)))
        self.assertFalse(auth.user_has_role(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.user_has_role(user, CourseInstructorRole(self.course.id)))
        self.assertFalse(CourseStaffRole(self.course.id).has_user(user))
示例#24
0
    def test_no_libraries(self):
        """
        Verify that only Course IDs are returned, not anything else like libraries.
        """
        # Make this user a course staff user for a course, AND a library.
        course_staff_user = self.create_user(username='******', is_staff=False)
        add_users(self.global_admin, CourseStaffRole(self.course.id), course_staff_user)
        add_users(
            self.global_admin,
            CourseStaffRole(LibraryLocator.from_string('library-v1:library_org+library_name')),
            course_staff_user,
        )

        # Requesting the courses should return *only* courses and not libraries.
        self.setup_user(self.staff_user)
        filtered_response = self.verify_response(params={
            'username': course_staff_user.username,
            'role': 'staff'
        })
        assert len(filtered_response.data['results']) == 1
        assert filtered_response.data['results'][0].startswith(self.course.org)
示例#25
0
 def test_add_user_to_course_group_permission_denied(self):
     """
     Verifies PermissionDenied if caller of add_user_to_course_group is not instructor role.
     """
     add_users(self.global_admin, CourseInstructorRole(self.course_key), self.creator)
     add_users(self.global_admin, CourseStaffRole(self.course_key), self.creator)
     with self.assertRaises(PermissionDenied):
         add_users(self.staff, CourseStaffRole(self.course_key), self.staff)
示例#26
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(user_has_role(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(user_has_role(self.creator, CourseInstructorRole(self.course_key)))

        # Add another user to the staff role.
        self.assertFalse(user_has_role(self.staff, CourseStaffRole(self.course_key)))
        add_users(self.creator, CourseStaffRole(self.course_key), self.staff)
        self.assertTrue(user_has_role(self.staff, CourseStaffRole(self.course_key)))
示例#27
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(user_has_role(self.staff, CourseStaffRole(self.course_key)))

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

        remove_users(self.creator, CourseInstructorRole(self.course_key), self.creator)
        self.assertFalse(user_has_role(self.creator, CourseInstructorRole(self.course_key)))
示例#28
0
 def test_add_user_to_group_requires_active(self):
     with pytest.raises(PermissionDenied):
         self.admin.is_active = False
         add_users(self.admin, CourseCreatorRole(), self.user)
    def test_get_all_users(self):
        """
        Test getting all authors for a course where their permissions run the gamut of allowed group
        types.
        """
        # first check the course creator.has explicit access (don't use has_access as is_staff
        # will trump the actual test)
        self.assertTrue(
            CourseInstructorRole(self.course_key).has_user(self.user),
            "Didn't add creator as instructor.")
        users = copy.copy(self.users)
        # doesn't use role.users_with_role b/c it's verifying the roles.py behavior
        user_by_role = {}
        # add the misc users to the course in different groups
        for role in [
                CourseInstructorRole, CourseStaffRole, OrgStaffRole,
                OrgInstructorRole
        ]:
            user_by_role[role] = []
            # Org-based roles are created via org name, rather than course_key
            if (role is OrgStaffRole) or (role is OrgInstructorRole):
                group = role(self.course_key.org)
            else:
                group = role(self.course_key)
            # NOTE: this loop breaks the roles.py abstraction by purposely assigning
            # users to one of each possible groupname in order to test that has_course_author_access
            # and remove_user work
            user = users.pop()
            group.add_users(user)
            user_by_role[role].append(user)
            self.assertTrue(auth.has_course_author_access(user, self.course_key), f"{user} does not have access")  # lint-amnesty, pylint: disable=line-too-long

        course_team_url = reverse_course_url('course_team_handler',
                                             self.course_key)
        response = self.client.get_html(course_team_url)
        for role in [CourseInstructorRole, CourseStaffRole
                     ]:  # Global and org-based roles don't appear on this page
            for user in user_by_role[role]:
                self.assertContains(response, user.email)

        # test copying course permissions
        copy_course_key = self.store.make_course_key('copyu',
                                                     'copydept.mycourse',
                                                     'myrun')
        for role in [
                CourseInstructorRole, CourseStaffRole, OrgStaffRole,
                OrgInstructorRole
        ]:
            if (role is OrgStaffRole) or (role is OrgInstructorRole):
                auth.add_users(self.user, role(copy_course_key.org),
                               *role(self.course_key.org).users_with_role())
            else:
                auth.add_users(self.user, role(copy_course_key),
                               *role(self.course_key).users_with_role())
        # verify access in copy course and verify that removal from source course w/ the various
        # groupnames works
        for role in [
                CourseInstructorRole, CourseStaffRole, OrgStaffRole,
                OrgInstructorRole
        ]:
            for user in user_by_role[role]:
                # forcefully decache the groups: premise is that any real request will not have
                # multiple objects repr the same user but this test somehow uses different instance
                # in above add_users call
                if hasattr(user, '_roles'):
                    del user._roles

                self.assertTrue(
                    auth.has_course_author_access(user, copy_course_key),
                    f"{user} no copy access")
                if (role is OrgStaffRole) or (role is OrgInstructorRole):
                    auth.remove_users(self.user, role(self.course_key.org),
                                      user)
                else:
                    auth.remove_users(self.user, role(self.course_key), user)
                self.assertFalse(auth.has_course_author_access(user, self.course_key), f"{user} remove didn't work")  # lint-amnesty, pylint: disable=line-too-long
示例#30
0
def _course_team_user(request, course_key, email):
    """
    Handle the add, remove, promote, demote requests ensuring the requester has authority
    """
    # check that logged in user has permissions to this item
    requester_perms = get_user_permissions(request.user, course_key)
    permissions_error_response = JsonResponse(
        {"error": _("Insufficient permissions")}, 403)
    if (requester_perms & STUDIO_VIEW_USERS) or (email == request.user.email):
        # This user has permissions to at least view the list of users or is editing themself
        pass
    else:
        # This user is not even allowed to know who the authorized users are.
        return permissions_error_response

    try:
        user = User.objects.get(email=email)
    except Exception:  # pylint: disable=broad-except
        msg = {
            "error":
            _("Could not find user by email address '{email}'.").format(
                email=email),
        }
        return JsonResponse(msg, 404)

    is_library = isinstance(course_key, LibraryLocator)
    # Ordered list of roles: can always move self to the right, but need STUDIO_EDIT_ROLES to move any user left
    if is_library:
        role_hierarchy = (CourseInstructorRole, CourseStaffRole,
                          LibraryUserRole)
    else:
        role_hierarchy = (CourseInstructorRole, CourseStaffRole)

    if request.method == "GET":
        # just return info about the user
        msg = {
            "email": user.email,
            "active": user.is_active,
            "role": None,
        }
        # what's the highest role that this user has? (How should this report global staff?)
        for role in role_hierarchy:
            if role(course_key).has_user(user):
                msg["role"] = role.ROLE
                break
        return JsonResponse(msg)

    # All of the following code is for editing/promoting/deleting users.
    # Check that the user has STUDIO_EDIT_ROLES permission or is editing themselves:
    if not ((requester_perms & STUDIO_EDIT_ROLES) or
            (user.id == request.user.id)):
        return permissions_error_response

    if request.method == "DELETE":
        new_role = None
    else:
        # only other operation supported is to promote/demote a user by changing their role:
        # role may be None or "" (equivalent to a DELETE request) but must be set.
        # Check that the new role was specified:
        if "role" in request.json or "role" in request.POST:
            new_role = request.json.get("role", request.POST.get("role"))
        else:
            return JsonResponse({"error": _("No `role` specified.")}, 400)

    # can't modify an inactive user but can remove it
    if not (user.is_active or new_role is None):
        msg = {
            "error":
            _('User {email} has registered but has not yet activated their account.'
              ).format(email=email),
        }
        return JsonResponse(msg, 400)

    old_roles = set()
    role_added = False
    for role_type in role_hierarchy:
        role = role_type(course_key)
        if role_type.ROLE == new_role:
            if (requester_perms
                    & STUDIO_EDIT_ROLES) or (user.id == request.user.id
                                             and old_roles):
                # User has STUDIO_EDIT_ROLES permission or
                # is currently a member of a higher role, and is thus demoting themself
                auth.add_users(request.user, role, user)
                role_added = True
            else:
                return permissions_error_response
        elif role.has_user(user, check_user_activation=False):  # pylint: disable=no-value-for-parameter
            # Remove the user from this old role:
            old_roles.add(role)

    if new_role and not role_added:
        return JsonResponse({"error": _("Invalid `role` specified.")}, 400)

    for role in old_roles:
        if isinstance(
                role,
                CourseInstructorRole) and role.users_with_role().count() == 1:
            msg = {
                "error":
                _("You may not remove the last Admin. Add another Admin first."
                  )
            }
            return JsonResponse(msg, 400)
        auth.remove_users(request.user, role, user)

    if new_role and not is_library:
        # The user may be newly added to this course.
        # auto-enroll the user in the course so that "View Live" will work.
        CourseEnrollment.enroll(user, course_key)

    return JsonResponse()