Merge pull request #2064 from edx/dhm/auth_consolidate
Replace authz with roles.py and reconcile their behaviors
This commit is contained in:
86
common/djangoapps/student/auth.py
Normal file
86
common/djangoapps/student/auth.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
The application interface to roles which checks whether any user trying to change
|
||||
authorization has authorization to do so, which infers authorization via role hierarchy
|
||||
(GlobalStaff is superset of auths of course instructor, ...), which consults the config
|
||||
to decide whether to check course creator role, and other such functions.
|
||||
"""
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.conf import settings
|
||||
|
||||
from student.roles import GlobalStaff, CourseCreatorRole, CourseStaffRole, CourseInstructorRole, CourseRole, \
|
||||
CourseBetaTesterRole
|
||||
|
||||
|
||||
def has_access(user, role):
|
||||
"""
|
||||
Check whether this user has access to this role (either direct or implied)
|
||||
:param user:
|
||||
:param role: an AccessRole
|
||||
"""
|
||||
if not user.is_active:
|
||||
return False
|
||||
# do cheapest check first even tho it's not the direct one
|
||||
if GlobalStaff().has_user(user):
|
||||
return True
|
||||
# CourseCreator is odd b/c it can be disabled via config
|
||||
if isinstance(role, CourseCreatorRole):
|
||||
# completely shut down course creation setting
|
||||
if settings.FEATURES.get('DISABLE_COURSE_CREATION', False):
|
||||
return False
|
||||
# wide open course creation setting
|
||||
if not settings.FEATURES.get('ENABLE_CREATOR_GROUP', False):
|
||||
return True
|
||||
|
||||
if role.has_user(user):
|
||||
return True
|
||||
# if not, then check inferred permissions
|
||||
if (isinstance(role, (CourseStaffRole, CourseBetaTesterRole)) and
|
||||
CourseInstructorRole(role.location).has_user(user)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def add_users(caller, role, *users):
|
||||
"""
|
||||
The caller requests adding the given users to the role. Checks that the caller
|
||||
has sufficient authority.
|
||||
|
||||
:param caller: a user
|
||||
:param role: an AccessRole
|
||||
"""
|
||||
_check_caller_authority(caller, role)
|
||||
role.add_users(*users)
|
||||
|
||||
|
||||
def remove_users(caller, role, *users):
|
||||
"""
|
||||
The caller requests removing the given users from the role. Checks that the caller
|
||||
has sufficient authority.
|
||||
|
||||
:param caller: a user
|
||||
:param role: an AccessRole
|
||||
"""
|
||||
# can always remove self (at this layer)
|
||||
if not(len(users) == 1 and caller == users[0]):
|
||||
_check_caller_authority(caller, role)
|
||||
role.remove_users(*users)
|
||||
|
||||
|
||||
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 has_access(caller, CourseInstructorRole(role.location)):
|
||||
raise PermissionDenied
|
||||
|
||||
242
common/djangoapps/student/roles.py
Normal file
242
common/djangoapps/student/roles.py
Normal file
@@ -0,0 +1,242 @@
|
||||
"""
|
||||
Classes used to model the roles used in the courseware. Each role is responsible for checking membership,
|
||||
adding users, removing users, and listing members
|
||||
"""
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from django.contrib.auth.models import User, Group
|
||||
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError
|
||||
from xmodule.modulestore.django import loc_mapper
|
||||
from xmodule.modulestore.locator import CourseLocator, Locator
|
||||
|
||||
|
||||
class CourseContextRequired(Exception):
|
||||
"""
|
||||
Raised when a course_context is required to determine permissions
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class AccessRole(object):
|
||||
"""
|
||||
Object representing a role with particular access to a resource
|
||||
"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def has_user(self, user): # pylint: disable=unused-argument
|
||||
"""
|
||||
Return whether the supplied django user has access to this role.
|
||||
"""
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
def add_users(self, *users):
|
||||
"""
|
||||
Add the role to the supplied django users.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def remove_users(self, *users):
|
||||
"""
|
||||
Remove the role from the supplied django users.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def users_with_role(self):
|
||||
"""
|
||||
Return a django QuerySet for all of the users with this role
|
||||
"""
|
||||
return User.objects.none()
|
||||
|
||||
|
||||
class GlobalStaff(AccessRole):
|
||||
"""
|
||||
The global staff role
|
||||
"""
|
||||
def has_user(self, user):
|
||||
return user.is_staff
|
||||
|
||||
def add_users(self, *users):
|
||||
for user in users:
|
||||
if (user.is_authenticated and user.is_active):
|
||||
user.is_staff = True
|
||||
user.save()
|
||||
|
||||
def remove_users(self, *users):
|
||||
for user in users:
|
||||
# don't check is_authenticated nor is_active on purpose
|
||||
user.is_staff = False
|
||||
user.save()
|
||||
|
||||
def users_with_role(self):
|
||||
raise Exception("This operation is un-indexed, and shouldn't be used")
|
||||
|
||||
|
||||
class GroupBasedRole(AccessRole):
|
||||
"""
|
||||
A role based on membership to any of a set of groups.
|
||||
"""
|
||||
def __init__(self, group_names):
|
||||
"""
|
||||
Create a GroupBasedRole from a list of group names
|
||||
|
||||
The first element of `group_names` will be the preferred group
|
||||
to use when adding a user to this Role.
|
||||
|
||||
If a user is a member of any of the groups in the list, then
|
||||
they will be consider a member of the Role
|
||||
"""
|
||||
self._group_names = [name.lower() for name in group_names]
|
||||
|
||||
def has_user(self, user):
|
||||
"""
|
||||
Return whether the supplied django user has access to this role.
|
||||
"""
|
||||
if not (user.is_authenticated and user.is_active):
|
||||
return False
|
||||
|
||||
# pylint: disable=protected-access
|
||||
if not hasattr(user, '_groups'):
|
||||
user._groups = set(name.lower() for name in user.groups.values_list('name', flat=True))
|
||||
|
||||
return len(user._groups.intersection(self._group_names)) > 0
|
||||
|
||||
def add_users(self, *users):
|
||||
"""
|
||||
Add the supplied django users to this role.
|
||||
"""
|
||||
# silently ignores anonymous and inactive users so that any that are
|
||||
# legit get updated.
|
||||
users = [user for user in users if user.is_authenticated and user.is_active]
|
||||
group, _ = Group.objects.get_or_create(name=self._group_names[0])
|
||||
group.user_set.add(*users)
|
||||
# remove cache
|
||||
for user in users:
|
||||
if hasattr(user, '_groups'):
|
||||
del user._groups
|
||||
|
||||
def remove_users(self, *users):
|
||||
"""
|
||||
Remove the supplied django users from this role.
|
||||
"""
|
||||
groups = Group.objects.filter(name__in=self._group_names)
|
||||
for group in groups:
|
||||
group.user_set.remove(*users)
|
||||
# remove cache
|
||||
for user in users:
|
||||
if hasattr(user, '_groups'):
|
||||
del user._groups
|
||||
|
||||
def users_with_role(self):
|
||||
"""
|
||||
Return a django QuerySet for all of the users with this role
|
||||
"""
|
||||
return User.objects.filter(groups__name__in=self._group_names)
|
||||
|
||||
|
||||
class CourseRole(GroupBasedRole):
|
||||
"""
|
||||
A named role in a particular course
|
||||
"""
|
||||
def __init__(self, role, location, course_context=None):
|
||||
"""
|
||||
Location may be either a Location, a string, dict, or tuple which Location will accept
|
||||
in its constructor, or a CourseLocator. Handle all these giving some preference to
|
||||
the preferred naming.
|
||||
"""
|
||||
# TODO: figure out how to make the group name generation lazy so it doesn't force the
|
||||
# loc mapping?
|
||||
self.location = Locator.to_locator_or_location(location)
|
||||
self.role = role
|
||||
# direct copy from auth.authz.get_all_course_role_groupnames will refactor to one impl asap
|
||||
groupnames = []
|
||||
|
||||
# pylint: disable=no-member
|
||||
if isinstance(self.location, Location):
|
||||
try:
|
||||
groupnames.append('{0}_{1}'.format(role, self.location.course_id))
|
||||
course_context = self.location.course_id # course_id is valid for translation
|
||||
except InvalidLocationError: # will occur on old locations where location is not of category course
|
||||
if course_context is None:
|
||||
raise CourseContextRequired()
|
||||
else:
|
||||
groupnames.append('{0}_{1}'.format(role, course_context))
|
||||
try:
|
||||
locator = loc_mapper().translate_location(course_context, self.location, False, False)
|
||||
groupnames.append('{0}_{1}'.format(role, locator.package_id))
|
||||
except (InvalidLocationError, ItemNotFoundError):
|
||||
# if it's never been mapped, the auth won't be via the Locator syntax
|
||||
pass
|
||||
# least preferred legacy role_course format
|
||||
groupnames.append('{0}_{1}'.format(role, self.location.course))
|
||||
elif isinstance(self.location, CourseLocator):
|
||||
groupnames.append('{0}_{1}'.format(role, self.location.package_id))
|
||||
# handle old Location syntax
|
||||
old_location = loc_mapper().translate_locator_to_location(self.location, get_course=True)
|
||||
if old_location:
|
||||
# the slashified version of the course_id (myu/mycourse/myrun)
|
||||
groupnames.append('{0}_{1}'.format(role, old_location.course_id))
|
||||
# add the least desirable but sometimes occurring format.
|
||||
groupnames.append('{0}_{1}'.format(role, old_location.course))
|
||||
|
||||
super(CourseRole, self).__init__(groupnames)
|
||||
|
||||
|
||||
class OrgRole(GroupBasedRole):
|
||||
"""
|
||||
A named role in a particular org
|
||||
"""
|
||||
def __init__(self, role, location):
|
||||
# pylint: disable=no-member
|
||||
|
||||
location = Location(location)
|
||||
super(OrgRole, self).__init__(['{}_{}'.format(role, location.org)])
|
||||
|
||||
|
||||
class CourseStaffRole(CourseRole):
|
||||
"""A Staff member of a course"""
|
||||
ROLE = 'staff'
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CourseStaffRole, self).__init__(self.ROLE, *args, **kwargs)
|
||||
|
||||
|
||||
class CourseInstructorRole(CourseRole):
|
||||
"""A course Instructor"""
|
||||
ROLE = 'instructor'
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CourseInstructorRole, self).__init__(self.ROLE, *args, **kwargs)
|
||||
|
||||
|
||||
class CourseBetaTesterRole(CourseRole):
|
||||
"""A course Beta Tester"""
|
||||
ROLE = 'beta_testers'
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CourseBetaTesterRole, self).__init__(self.ROLE, *args, **kwargs)
|
||||
|
||||
|
||||
class OrgStaffRole(OrgRole):
|
||||
"""An organization staff member"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OrgStaffRole, self).__init__('staff', *args, **kwargs)
|
||||
|
||||
|
||||
class OrgInstructorRole(OrgRole):
|
||||
"""An organization instructor"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OrgInstructorRole, self).__init__('instructor', *args, **kwargs)
|
||||
|
||||
|
||||
class CourseCreatorRole(GroupBasedRole):
|
||||
"""
|
||||
This is the group of people who have permission to create new courses (we may want to eventually
|
||||
make this an org based role).
|
||||
"""
|
||||
ROLE = "course_creator_group"
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CourseCreatorRole, self).__init__(self.ROLE, *args, **kwargs)
|
||||
189
common/djangoapps/student/tests/test_authz.py
Normal file
189
common/djangoapps/student/tests/test_authz.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
Tests authz.py
|
||||
"""
|
||||
import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
from xmodule.modulestore import Location
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
from student.roles import CourseInstructorRole, CourseStaffRole, CourseCreatorRole
|
||||
from student.tests.factories import AdminFactory
|
||||
from student.auth import has_access, add_users, remove_users
|
||||
|
||||
|
||||
class CreatorGroupTest(TestCase):
|
||||
"""
|
||||
Tests for the course creator group.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
""" Test case setup """
|
||||
self.user = User.objects.create_user('testuser', 'test+courses@edx.org', 'foo')
|
||||
self.admin = User.objects.create_user('Mark', 'admin+courses@edx.org', 'foo')
|
||||
self.admin.is_staff = True
|
||||
|
||||
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()))
|
||||
|
||||
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()))
|
||||
|
||||
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', 'test+courses2@edx.org', '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()))
|
||||
|
||||
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()))
|
||||
|
||||
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}):
|
||||
self.user.is_authenticated = False
|
||||
add_users(self.admin, CourseCreatorRole(), self.user)
|
||||
self.assertFalse(has_access(self.user, CourseCreatorRole()))
|
||||
|
||||
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()))
|
||||
|
||||
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)
|
||||
|
||||
def test_add_user_to_group_requires_active(self):
|
||||
with self.assertRaises(PermissionDenied):
|
||||
self.admin.is_active = False
|
||||
add_users(self.admin, CourseCreatorRole(), self.user)
|
||||
|
||||
def test_add_user_to_group_requires_authenticated(self):
|
||||
with self.assertRaises(PermissionDenied):
|
||||
self.admin.is_authenticated = False
|
||||
add_users(self.admin, CourseCreatorRole(), self.user)
|
||||
|
||||
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)
|
||||
|
||||
def test_remove_user_from_group_requires_active(self):
|
||||
with self.assertRaises(PermissionDenied):
|
||||
self.admin.is_active = False
|
||||
remove_users(self.admin, CourseCreatorRole(), self.user)
|
||||
|
||||
def test_remove_user_from_group_requires_authenticated(self):
|
||||
with self.assertRaises(PermissionDenied):
|
||||
self.admin.is_authenticated = False
|
||||
remove_users(self.admin, CourseCreatorRole(), self.user)
|
||||
|
||||
|
||||
class CourseGroupTest(TestCase):
|
||||
"""
|
||||
Tests for instructor and staff groups for a particular course.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
""" Test case setup """
|
||||
self.global_admin = AdminFactory()
|
||||
self.creator = User.objects.create_user('testcreator', 'testcreator+courses@edx.org', 'foo')
|
||||
self.staff = User.objects.create_user('teststaff', 'teststaff+courses@edx.org', 'foo')
|
||||
self.location = Location('i4x', 'mitX', '101', 'course', 'test')
|
||||
|
||||
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.location)))
|
||||
add_users(self.global_admin, CourseInstructorRole(self.location), self.creator)
|
||||
add_users(self.global_admin, CourseStaffRole(self.location), self.creator)
|
||||
self.assertTrue(has_access(self.creator, CourseInstructorRole(self.location)))
|
||||
|
||||
# Add another user to the staff role.
|
||||
self.assertFalse(has_access(self.staff, CourseStaffRole(self.location)))
|
||||
add_users(self.creator, CourseStaffRole(self.location), self.staff)
|
||||
self.assertTrue(has_access(self.staff, CourseStaffRole(self.location)))
|
||||
|
||||
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.location), self.creator)
|
||||
add_users(self.global_admin, CourseStaffRole(self.location), self.creator)
|
||||
with self.assertRaises(PermissionDenied):
|
||||
add_users(self.staff, CourseStaffRole(self.location), self.staff)
|
||||
|
||||
def test_remove_user_from_course_group(self):
|
||||
"""
|
||||
Tests removing user from course group (happy path).
|
||||
"""
|
||||
add_users(self.global_admin, CourseInstructorRole(self.location), self.creator)
|
||||
add_users(self.global_admin, CourseStaffRole(self.location), self.creator)
|
||||
|
||||
add_users(self.creator, CourseStaffRole(self.location), self.staff)
|
||||
self.assertTrue(has_access(self.staff, CourseStaffRole(self.location)))
|
||||
|
||||
remove_users(self.creator, CourseStaffRole(self.location), self.staff)
|
||||
self.assertFalse(has_access(self.staff, CourseStaffRole(self.location)))
|
||||
|
||||
remove_users(self.creator, CourseInstructorRole(self.location), self.creator)
|
||||
self.assertFalse(has_access(self.creator, CourseInstructorRole(self.location)))
|
||||
|
||||
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.location), self.creator)
|
||||
another_staff = User.objects.create_user('another', 'teststaff+anothercourses@edx.org', 'foo')
|
||||
add_users(self.global_admin, CourseStaffRole(self.location), self.creator, self.staff, another_staff)
|
||||
with self.assertRaises(PermissionDenied):
|
||||
remove_users(self.staff, CourseStaffRole(self.location), another_staff)
|
||||
47
common/djangoapps/student/tests/test_roles.py
Normal file
47
common/djangoapps/student/tests/test_roles.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Tests of student.roles
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from xmodule.modulestore import Location
|
||||
from courseware.tests.factories import UserFactory, StaffFactory, InstructorFactory
|
||||
from student.tests.factories import AnonymousUserFactory
|
||||
|
||||
from student.roles import GlobalStaff, CourseRole
|
||||
|
||||
|
||||
class RolesTestCase(TestCase):
|
||||
"""
|
||||
Tests of student.roles
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.course = Location('i4x://edX/toy/course/2012_Fall')
|
||||
self.anonymous_user = AnonymousUserFactory()
|
||||
self.student = UserFactory()
|
||||
self.global_staff = UserFactory(is_staff=True)
|
||||
self.course_staff = StaffFactory(course=self.course)
|
||||
self.course_instructor = InstructorFactory(course=self.course)
|
||||
|
||||
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 test_group_name_case_insensitive(self):
|
||||
uppercase_loc = "i4x://ORG/COURSE/course/NAME"
|
||||
lowercase_loc = uppercase_loc.lower()
|
||||
|
||||
lowercase_group = "role_org/course/name"
|
||||
uppercase_group = lowercase_group.upper()
|
||||
|
||||
lowercase_user = UserFactory(groups=lowercase_group)
|
||||
uppercase_user = UserFactory(groups=uppercase_group)
|
||||
|
||||
self.assertTrue(CourseRole("role", lowercase_loc).has_user(lowercase_user))
|
||||
self.assertTrue(CourseRole("role", uppercase_loc).has_user(lowercase_user))
|
||||
self.assertTrue(CourseRole("role", lowercase_loc).has_user(uppercase_user))
|
||||
self.assertTrue(CourseRole("role", uppercase_loc).has_user(uppercase_user))
|
||||
|
||||
Reference in New Issue
Block a user