fixing unit tests fixing merge error fixing xqueue submission issue with unicode url (trial 0.1) fixing fotmats as commented upon removing yaml file language selection Unicode changes to support QRF removed unnecessary pass in modulestore/init.py fixing merge error fixing fotmats as commented upon removing yaml file language selection fixing pep8 violations - fixing pylint violations pylint violation fixing line spaces and formats ignore pylint E1101 remove empty line fixing pylint violations pep8 violations bulk mail unicode/decode fix migration error fix pep8 just to push again more unicode/decode Final changes to comments and error messages.
241 lines
8.1 KiB
Python
241 lines
8.1 KiB
Python
"""
|
|
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 = []
|
|
|
|
if isinstance(self.location, Location):
|
|
try:
|
|
groupnames.append(u'{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(u'{0}_{1}'.format(role, course_context))
|
|
try:
|
|
locator = loc_mapper().translate_location_to_course_locator(course_context, self.location)
|
|
groupnames.append(u'{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(u'{0}_{1}'.format(role, self.location.course)) # pylint: disable=E1101, E1103
|
|
elif isinstance(self.location, CourseLocator):
|
|
groupnames.append(u'{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(u'{0}_{1}'.format(role, old_location.course_id))
|
|
# add the least desirable but sometimes occurring format.
|
|
groupnames.append(u'{0}_{1}'.format(role, old_location.course)) # pylint: disable=E1101, E1103
|
|
|
|
super(CourseRole, self).__init__(groupnames)
|
|
|
|
|
|
class OrgRole(GroupBasedRole):
|
|
"""
|
|
A named role in a particular org
|
|
"""
|
|
def __init__(self, role, location):
|
|
location = Location(location)
|
|
super(OrgRole, self).__init__([u'{}_{}'.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)
|