Replace authz fns with roles.py ones
STUD-1006
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
|
||||
|
||||
@@ -64,11 +64,13 @@ class GlobalStaff(AccessRole):
|
||||
|
||||
def add_users(self, *users):
|
||||
for user in users:
|
||||
user.is_staff = True
|
||||
user.save()
|
||||
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()
|
||||
|
||||
@@ -96,10 +98,10 @@ class GroupBasedRole(AccessRole):
|
||||
"""
|
||||
Return whether the supplied django user has access to this role.
|
||||
"""
|
||||
# pylint: disable=protected-access
|
||||
if not user.is_authenticated():
|
||||
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))
|
||||
|
||||
@@ -109,8 +111,12 @@ class GroupBasedRole(AccessRole):
|
||||
"""
|
||||
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
|
||||
@@ -119,8 +125,10 @@ class GroupBasedRole(AccessRole):
|
||||
"""
|
||||
Remove the supplied django users from this role.
|
||||
"""
|
||||
group, _ = Group.objects.get_or_create(name=self._group_names[0])
|
||||
group.user_set.remove(*users)
|
||||
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
|
||||
@@ -144,31 +152,33 @@ class CourseRole(GroupBasedRole):
|
||||
"""
|
||||
# TODO: figure out how to make the group name generation lazy so it doesn't force the
|
||||
# loc mapping?
|
||||
location = Locator.to_locator_or_location(location)
|
||||
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(location, Location):
|
||||
if isinstance(self.location, Location):
|
||||
try:
|
||||
groupnames.append('{0}_{1}'.format(role, location.course_id))
|
||||
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(location.course_id, location, False, False)
|
||||
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, location.course))
|
||||
elif isinstance(location, CourseLocator):
|
||||
groupnames.append('{0}_{1}'.format(role, location.package_id))
|
||||
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(location, get_course=True)
|
||||
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))
|
||||
@@ -191,20 +201,23 @@ class OrgRole(GroupBasedRole):
|
||||
|
||||
class CourseStaffRole(CourseRole):
|
||||
"""A Staff member of a course"""
|
||||
ROLE = 'staff'
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CourseStaffRole, self).__init__('staff', *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__('instructor', *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__('beta_testers', *args, **kwargs)
|
||||
super(CourseBetaTesterRole, self).__init__(self.ROLE, *args, **kwargs)
|
||||
|
||||
|
||||
class OrgStaffRole(OrgRole):
|
||||
@@ -217,3 +230,13 @@ 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)
|
||||
|
||||
Reference in New Issue
Block a user