by overriding can_load_courseware if the MFE is disabled for the user If the user would be allowed to see the courseware MFE (can_load_courseware), we check whether the MFE is disabled for them, based on global settings, course settings (mongo courses), or their particular bucketing in our ExperimentWaffleFlag. If we determine they shouldn’t be allowed to see it, we return a new CoursewareMicrofrontendDisabledAccessError access response, which the MFE will use to know it should redirect to the old LMS experience. Fixes: TNL-7362 Co-authored-by: stvn <stvn@mit.edu>
250 lines
9.6 KiB
Python
250 lines
9.6 KiB
Python
"""
|
|
This file contains all the classes used by has_access for error handling
|
|
"""
|
|
|
|
|
|
from django.utils.translation import ugettext as _
|
|
|
|
from xmodule.course_metadata_utils import DEFAULT_START_DATE
|
|
|
|
|
|
class AccessResponse(object):
|
|
"""Class that represents a response from a has_access permission check."""
|
|
def __init__(self, has_access, error_code=None, developer_message=None, user_message=None,
|
|
additional_context_user_message=None, user_fragment=None):
|
|
"""
|
|
Creates an AccessResponse object.
|
|
|
|
Arguments:
|
|
has_access (bool): if the user is granted access or not
|
|
error_code (String): optional - default is None. Unique identifier
|
|
for the specific type of error
|
|
developer_message (String): optional - default is None. Message
|
|
to show the developer
|
|
user_message (String): optional - default is None. Message to
|
|
show the user
|
|
additional_context_user_message (String): optional - default is None. Message to
|
|
show the user when additional context like the course name is necessary
|
|
user_fragment (:py:class:`~web_fragments.fragment.Fragment`): optional -
|
|
An html fragment to display to the user if their access is denied
|
|
"""
|
|
self.has_access = has_access
|
|
self.error_code = error_code
|
|
self.developer_message = developer_message
|
|
self.user_message = user_message
|
|
self.additional_context_user_message = additional_context_user_message
|
|
self.user_fragment = user_fragment
|
|
if has_access:
|
|
assert error_code is None
|
|
|
|
def __bool__(self):
|
|
"""
|
|
Overrides bool().
|
|
|
|
Allows for truth value testing of AccessResponse objects, so callers
|
|
who do not need the specific error information can check if access
|
|
is granted.
|
|
|
|
Returns:
|
|
bool: whether or not access is granted
|
|
|
|
"""
|
|
return self.has_access
|
|
|
|
__nonzero__ = __bool__
|
|
|
|
def to_json(self):
|
|
"""
|
|
Creates a serializable JSON representation of an AccessResponse object.
|
|
|
|
Returns:
|
|
dict: JSON representation
|
|
"""
|
|
return {
|
|
"has_access": self.has_access,
|
|
"error_code": self.error_code,
|
|
"developer_message": self.developer_message,
|
|
"user_message": self.user_message,
|
|
"additional_context_user_message": self.additional_context_user_message,
|
|
"user_fragment": self.user_fragment,
|
|
}
|
|
|
|
def __repr__(self):
|
|
return "AccessResponse({!r}, {!r}, {!r}, {!r}, {!r}, {!r})".format(
|
|
self.has_access,
|
|
self.error_code,
|
|
self.developer_message,
|
|
self.user_message,
|
|
self.additional_context_user_message,
|
|
self.user_fragment,
|
|
)
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, AccessResponse):
|
|
return False
|
|
|
|
return (
|
|
self.has_access == other.has_access and
|
|
self.error_code == other.error_code and
|
|
self.developer_message == other.developer_message and
|
|
self.user_message == other.user_message and
|
|
self.additional_context_user_message == other.additional_context_user_message and
|
|
self.user_fragment == other.user_fragment
|
|
)
|
|
|
|
|
|
class AccessError(AccessResponse):
|
|
"""
|
|
Class that holds information about the error in the case of an access
|
|
denial in has_access. Contains the error code, user and developer
|
|
messages. Subclasses represent specific errors.
|
|
"""
|
|
def __init__(self, error_code, developer_message, user_message,
|
|
additional_context_user_message=None, user_fragment=None):
|
|
"""
|
|
Creates an AccessError object.
|
|
|
|
An AccessError object represents an AccessResponse where access is
|
|
denied (has_access is False).
|
|
|
|
Arguments:
|
|
error_code (String): unique identifier for the specific type of
|
|
error developer_message (String): message to show the developer
|
|
user_message (String): message to show the user
|
|
additional_context_user_message (String): message to show user with additional context like the course name
|
|
user_fragment (:py:class:`~web_fragments.fragment.Fragment`): HTML to show the user
|
|
|
|
"""
|
|
super(AccessError, self).__init__(False, error_code, developer_message, user_message,
|
|
additional_context_user_message, user_fragment)
|
|
|
|
|
|
class StartDateError(AccessError):
|
|
"""
|
|
Access denied because the course has not started yet and the user
|
|
is not staff
|
|
"""
|
|
def __init__(self, start_date, display_error_to_user=True):
|
|
"""
|
|
Arguments:
|
|
display_error_to_user: If True, display this error to users in the UI.
|
|
"""
|
|
error_code = "course_not_started"
|
|
if start_date == DEFAULT_START_DATE:
|
|
developer_message = u"Course has not started"
|
|
user_message = _(u"Course has not started")
|
|
else:
|
|
developer_message = u"Course does not start until {}".format(start_date)
|
|
user_message = _(u"Course does not start until {}"
|
|
.format(u"{:%B %d, %Y}".format(start_date)))
|
|
super(StartDateError, self).__init__(
|
|
error_code,
|
|
developer_message,
|
|
user_message if display_error_to_user else None
|
|
)
|
|
|
|
|
|
class MilestoneAccessError(AccessError):
|
|
"""
|
|
Access denied because the user has unfulfilled milestones
|
|
"""
|
|
def __init__(self):
|
|
error_code = "unfulfilled_milestones"
|
|
developer_message = u"User has unfulfilled milestones"
|
|
user_message = _(u"You have unfulfilled milestones")
|
|
super(MilestoneAccessError, self).__init__(error_code, developer_message, user_message)
|
|
|
|
|
|
class VisibilityError(AccessError):
|
|
"""
|
|
Access denied because the user does have the correct role to view this
|
|
course.
|
|
"""
|
|
def __init__(self, display_error_to_user=True):
|
|
"""
|
|
Arguments:
|
|
display_error_to_user: Should a message showing that access was denied to this content
|
|
be shown to the user?
|
|
"""
|
|
error_code = "not_visible_to_user"
|
|
developer_message = u"Course is not visible to this user"
|
|
user_message = _(u"You do not have access to this course")
|
|
super(VisibilityError, self).__init__(
|
|
error_code,
|
|
developer_message,
|
|
user_message if display_error_to_user else None
|
|
)
|
|
|
|
|
|
class MobileAvailabilityError(AccessError):
|
|
"""
|
|
Access denied because the course is not available on mobile for the user
|
|
"""
|
|
def __init__(self):
|
|
error_code = "mobile_unavailable"
|
|
developer_message = u"Course is not available on mobile for this user"
|
|
user_message = _(u"You do not have access to this course on a mobile device")
|
|
super(MobileAvailabilityError, self).__init__(error_code, developer_message, user_message)
|
|
|
|
|
|
class IncorrectPartitionGroupError(AccessError):
|
|
"""
|
|
Access denied because the user is not in the correct user subset.
|
|
"""
|
|
def __init__(self, partition, user_group, allowed_groups, user_message=None, user_fragment=None):
|
|
error_code = "incorrect_user_group"
|
|
developer_message = u"In partition {}, user was in group {}, but only {} are allowed access".format(
|
|
partition.name,
|
|
user_group.name if user_group is not None else user_group,
|
|
u", ".join(group.name for group in allowed_groups),
|
|
)
|
|
super(IncorrectPartitionGroupError, self).__init__(
|
|
error_code=error_code,
|
|
developer_message=developer_message,
|
|
user_message=user_message,
|
|
user_fragment=user_fragment
|
|
)
|
|
|
|
|
|
class NoAllowedPartitionGroupsError(AccessError):
|
|
"""
|
|
Access denied because the content is not allowed to any group in a partition.
|
|
"""
|
|
def __init__(self, partition, user_message=None, user_fragment=None):
|
|
error_code = "no_allowed_user_groups"
|
|
developer_message = u"Group access for {} excludes all students".format(partition.name)
|
|
super(NoAllowedPartitionGroupsError, self).__init__(error_code, developer_message, user_message)
|
|
|
|
|
|
class EnrollmentRequiredAccessError(AccessError):
|
|
"""
|
|
Access denied because the user must be enrolled in the course
|
|
"""
|
|
def __init__(self):
|
|
error_code = "enrollment_required"
|
|
developer_message = u"User must be enrolled in the course"
|
|
user_message = _(u"You must be enrolled in the course")
|
|
super(EnrollmentRequiredAccessError, self).__init__(error_code, developer_message, user_message)
|
|
|
|
|
|
class AuthenticationRequiredAccessError(AccessError):
|
|
"""
|
|
Access denied because the user must be authenticated to see it
|
|
"""
|
|
def __init__(self):
|
|
error_code = "authentication_required"
|
|
developer_message = u"User must be authenticated to view the course"
|
|
user_message = _(u"You must be logged in to see this course")
|
|
super(AuthenticationRequiredAccessError, self).__init__(error_code, developer_message, user_message)
|
|
|
|
|
|
class CoursewareMicrofrontendDisabledAccessError(AccessError):
|
|
"""
|
|
Access denied because the courseware micro-frontend is disabled for this user.
|
|
"""
|
|
def __init__(self):
|
|
error_code = 'microfrontend_disabled'
|
|
developer_message = u'Micro-frontend is disabled for this user'
|
|
user_message = _(u'Please view your course in the existing experience')
|
|
super(CoursewareMicrofrontendDisabledAccessError, self).__init__(error_code, developer_message, user_message)
|