def delete(self, request, course_key_string): course_key = CourseKey.from_string(course_key_string) email = request.data.get("email", None) validate_param_exist(email, "email") 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 Response(msg, 404) auth.get_user_permissions(request.user, course_key) auth.remove_users(request.user, CourseStaffRole(course_key), user) auth.remove_users(request.user, CourseInstructorRole(course_key), user) CourseEnrollment.unenroll(user, course_key) msg = "'{email}''s permissions are revoked from '{course_key}'".format( email=email, course_key=course_key) log.info(msg) return Response( {'message': "User is removed from {}.".format(course_key)})
def test_remove_user_from_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 remove_users(self.admin, CourseCreatorRole(), self.user)
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)
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)
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()))
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)))
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)
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()))
def test_remove_user_from_group_requires_active(self): with pytest.raises(PermissionDenied): self.admin.is_active = False remove_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
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()
def test_remove_user_from_group_requires_staff_access(self): with self.assertRaises(PermissionDenied): self.admin.is_staff = False remove_users(self.admin, CourseCreatorRole(), self.user)