Files
edx-platform/lms/djangoapps/courseware/rules.py

180 lines
6.1 KiB
Python

"""
django-rules and Bridgekeeper rules for courseware related features
"""
import logging
import traceback
import laboratory
import rules
import six
from bridgekeeper.rules import EMPTY, Rule
from django.conf import settings
from django.db.models import Q
from opaque_keys.edx.django.models import CourseKeyField
from opaque_keys.edx.keys import CourseKey, UsageKey
from xblock.core import XBlock
from course_modes.models import CourseMode
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from student.models import CourseAccessRole, CourseEnrollment
from student.roles import CourseRole, OrgRole
from xmodule.course_module import CourseDescriptor
from xmodule.error_module import ErrorDescriptor
from xmodule.x_module import XModule
from .access import has_access
LOG = logging.getLogger(__name__)
@rules.predicate
def is_track_ok_for_exam(user, exam):
"""
Returns whether the user is in an appropriate enrollment mode
"""
course_id = CourseKey.from_string(exam['course_id'])
mode, is_active = CourseEnrollment.enrollment_mode_for_user(user, course_id)
return is_active and mode in (CourseMode.VERIFIED, CourseMode.MASTERS, CourseMode.PROFESSIONAL)
# The edx_proctoring.api uses this permission to gate access to the
# proctored experience
can_take_proctored_exam = is_track_ok_for_exam
rules.set_perm('edx_proctoring.can_take_proctored_exam', is_track_ok_for_exam)
class HasAccessRule(Rule):
"""
A rule that calls `has_access` to determine whether it passes
"""
def __init__(self, action):
self.action = action
def check(self, user, instance=None):
return has_access(user, self.action, instance)
def query(self, user):
# Return an always-empty queryset filter so that this always
# fails permissions, but still passes the is_possible_for check
# that is used to determine if the rule should allow a user
# into django admin
return Q(pk__in=[])
class StaffAccessExperiment(laboratory.Experiment):
def compare(self, control, candidate):
return bool(control.value) == candidate.value
def publish(self, result):
if not result.match:
LOG.warning(
u"StaffAccessExperiment: control=%r, candidate=%r\n%s",
result.control,
result.candidates[0],
"".join(traceback.format_stack(limit=10))
)
class HasStaffAccessToContent(Rule):
"""
Check whether a user has `staff` access in a course.
Expects to be used to filter a CourseOverview queryset
"""
def check(self, user, instance=None):
"""
Return True if the supplied user has staff-level access to the supplied content.
"""
staff_sql_experiment = StaffAccessExperiment(
raise_on_mismatch=settings.DEBUG,
context={'userid': user.id, 'instance': repr(instance)}
)
staff_sql_experiment.control(self._check_with_has_access, args=(user, instance))
staff_sql_experiment.candidate(self._check_with_query, args=(user, instance))
return staff_sql_experiment.conduct()
def _check_with_has_access(self, user, instance=None):
return has_access(user, 'staff', instance)
def _check_with_query(self, user, instance=None):
"""
Use the query method to check whether a single user has access to the supplied object.
"""
# delegate the work to type-specific functions.
# (start with more specific types, then get more general)
if isinstance(instance, (CourseDescriptor, CourseOverview)):
course_key = instance.id
elif isinstance(instance, (ErrorDescriptor, XModule, XBlock)):
course_key = instance.scope_ids.usage_id.course_key
elif isinstance(instance, CourseKey):
course_key = instance
elif isinstance(instance, UsageKey):
course_key = instance.course_key
elif isinstance(instance, six.string_types):
course_key = CourseKey.from_string(instance)
return self.filter(user, CourseOverview.objects.filter(id=course_key)).exists()
def query(self, user):
"""
Returns a Q object that expects to be used to filter CourseOverview queries.
"""
if not user.is_authenticated:
return EMPTY
masq_settings = getattr(user, 'masquerade_settings', {})
masq_as_student = [
course_key for
(course_key, masq_setting) in masq_settings.items()
if masq_setting.role == 'student'
]
not_masquerading_as_student = ~Q(id__in=masq_as_student)
is_global_staff = user.is_staff
course_staff_or_instructor_courses = CourseAccessRole.objects.filter(
user=user,
role__in=('staff', 'instructor')
).exclude(
course_id=CourseKeyField.Empty,
).values('course_id')
org_staff_or_instructor_courses = CourseAccessRole.objects.filter(
user=user,
role__in=('staff', 'instructor'),
course_id=CourseKeyField.Empty,
org__isnull=False
).values('org')
query = not_masquerading_as_student
if not is_global_staff:
query &= Q(id__in=course_staff_or_instructor_courses) | Q(org__in=org_staff_or_instructor_courses)
return query
class HasRolesRule(Rule):
def __init__(self, *roles):
self.roles = roles
def check(self, user, instance=None):
if not user.is_authenticated:
return False
if isinstance(instance, CourseKey):
course_key = instance
elif isinstance(instance, (CourseDescriptor, CourseOverview)):
course_key = instance.id
elif isinstance(instance, (ErrorDescriptor, XModule, XBlock)):
course_key = instance.scope_ids.usage_id.course_key
else:
course_key = CourseKey.from_string(str(instance))
for role in self.roles:
if CourseRole(role, course_key).has_user(user):
return True
if OrgRole(role, course_key.org).has_user(user):
return True
return False