Merge pull request #19534 from edx/REVMI-31

clean up where access checks happen
This commit is contained in:
Matthew Piatetsky
2019-01-08 10:27:20 -05:00
committed by GitHub
6 changed files with 77 additions and 66 deletions

View File

@@ -17,6 +17,14 @@ from student.roles import (
OrgInstructorRole,
GlobalStaff
)
from xmodule.partitions.partitions import Group
CONTENT_TYPE_GATE_GROUP_IDS = {
'limited_access': 1,
'full_access': 2,
}
LIMITED_ACCESS = Group(CONTENT_TYPE_GATE_GROUP_IDS['limited_access'], 'Limited-access Users')
FULL_ACCESS = Group(CONTENT_TYPE_GATE_GROUP_IDS['full_access'], 'Full-access Users')
def has_staff_roles(user, course_key):

View File

@@ -5,21 +5,28 @@ Content Type Gating Configuration Models
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from lms.djangoapps.courseware.masquerade import get_course_masquerade, is_masquerading_as_specific_student
from course_modes.models import CourseMode
from lms.djangoapps.courseware.masquerade import (
get_course_masquerade,
get_masquerading_user_group,
is_masquerading_as_specific_student,
)
from openedx.core.djangoapps.config_model_utils.models import StackedConfigurationModel
from openedx.core.djangoapps.config_model_utils.utils import is_in_holdback
from openedx.features.content_type_gating.helpers import has_staff_roles
from openedx.features.content_type_gating.helpers import FULL_ACCESS, has_staff_roles, LIMITED_ACCESS
from openedx.features.course_duration_limits.config import (
CONTENT_TYPE_GATING_FLAG,
FEATURE_BASED_ENROLLMENT_GLOBAL_KILL_FLAG,
)
from student.models import CourseEnrollment
from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
@python_2_unicode_compatible
@@ -51,7 +58,36 @@ class ContentTypeGatingConfig(StackedConfigurationModel):
)
@classmethod
def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None):
def has_full_access_role_in_masquerade(cls, user, course_key, course_masquerade, student_masquerade,
user_partition):
"""
The roles of the masquerade user are used to determine whether the content gate displays.
The gate will not appear if the masquerade user has any of the following roles:
Staff, Instructor, Beta Tester, Forum Community TA, Forum Group Moderator, Forum Moderator, Forum Administrator
"""
if student_masquerade:
# If a request is masquerading as a specific user, the user variable will represent the correct user.
if user and user.id and has_staff_roles(user, course_key):
return True
elif user_partition:
# If the current user is masquerading as a generic student in a specific group,
# then return the value based on that group.
masquerade_group = get_masquerading_user_group(course_key, user, user_partition)
if masquerade_group is None:
audit_mode_id = settings.COURSE_ENROLLMENT_MODES.get(CourseMode.AUDIT, {}).get('id')
# We are checking the user partition id here because currently content
# cannot have both the enrollment track partition and content gating partition
# configured simultaneously. We may change this in the future and allow
# configuring both partitions on content and selecting both partitions in masquerade.
if course_masquerade.user_partition_id == ENROLLMENT_TRACK_PARTITION_ID:
return course_masquerade.group_id != audit_mode_id
elif masquerade_group is FULL_ACCESS:
return True
elif masquerade_group is LIMITED_ACCESS:
return False
@classmethod
def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None, user_partition=None):
"""
Return whether Content Type Gating is enabled for this enrollment.
@@ -91,20 +127,18 @@ class ContentTypeGatingConfig(StackedConfigurationModel):
if user is None and enrollment is not None:
user = enrollment.user
no_masquerade = get_course_masquerade(user, course_key) is None
course_masquerade = get_course_masquerade(user, course_key)
no_masquerade = course_masquerade is None
student_masquerade = is_masquerading_as_specific_student(user, course_key)
# We can only use the user variable for the code below when the request is not in a masquerade state
# or is masquerading as a specific user.
# When a request is not in a masquerade state the user variable represents the correct user.
# When a request is in a masquerade state and not masquerading as a specific user,
# then then user variable will be the incorrect (original) user, not the masquerade user.
# If a request is masquerading as a specific user, the user variable will represent the correct user.
user_variable_represents_correct_user = (no_masquerade or student_masquerade)
if user and user.id:
# TODO: Move masquerade checks to enabled_for_enrollment from content_type_gating/partitions.py
# TODO: Consolidate masquerade checks into shared function like has_staff_roles below
if user_variable_represents_correct_user and has_staff_roles(user, course_key):
if course_masquerade:
if cls.has_full_access_role_in_masquerade(user, course_key, course_masquerade, student_masquerade,
user_partition):
return False
# When a request is not in a masquerade state the user variable represents the correct user.
elif user and user.id and has_staff_roles(user, course_key):
return False
# check if user is in holdback
if user_variable_represents_correct_user and is_in_holdback(user):
@@ -113,7 +147,7 @@ class ContentTypeGatingConfig(StackedConfigurationModel):
# enrollment might be None if the user isn't enrolled. In that case,
# return enablement as if the user enrolled today
# Also, ignore enrollment creation date if the user is masquerading.
if enrollment is None or not no_masquerade:
if enrollment is None or course_masquerade:
return cls.enabled_for_course(course_key=course_key, target_datetime=timezone.now())
else:
current_config = cls.current(course_key=enrollment.course_id)

View File

@@ -7,24 +7,18 @@ of audit learners.
import logging
from course_modes.models import CourseMode
import crum
from django.apps import apps
from django.conf import settings
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _
from web_fragments.fragment import Fragment
from course_modes.models import CourseMode
from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.courseware.masquerade import (
get_course_masquerade,
is_masquerading_as_specific_student,
get_masquerading_user_group,
)
from xmodule.partitions.partitions import Group, UserPartition, UserPartitionError, ENROLLMENT_TRACK_PARTITION_ID
from xmodule.partitions.partitions import UserPartition, UserPartitionError
from openedx.core.lib.mobile_utils import is_request_from_mobile_app
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.content_type_gating.helpers import FULL_ACCESS, LIMITED_ACCESS
LOG = logging.getLogger(__name__)
@@ -33,12 +27,6 @@ LOG = logging.getLogger(__name__)
CONTENT_GATING_PARTITION_ID = 51
CONTENT_TYPE_GATE_GROUP_IDS = {
'limited_access': 1,
'full_access': 2,
}
def create_content_gating_partition(course):
"""
Create and return the Content Gating user partition.
@@ -124,9 +112,6 @@ class ContentTypeGatingPartitionScheme(object):
the gated content despite not being verified users.
"""
LIMITED_ACCESS = Group(CONTENT_TYPE_GATE_GROUP_IDS['limited_access'], 'Limited-access Users')
FULL_ACCESS = Group(CONTENT_TYPE_GATE_GROUP_IDS['full_access'], 'Full-access Users')
read_only = True
@classmethod
@@ -135,29 +120,12 @@ class ContentTypeGatingPartitionScheme(object):
Returns the Group for the specified user.
"""
# First, check if we have to deal with masquerading.
# If the current user is masquerading as a specific student, use the
# same logic as normal to return that student's group. If the current
# user is masquerading as a generic student in a specific group, then
# return that group.
course_masquerade = get_course_masquerade(user, course_key)
if course_masquerade and not is_masquerading_as_specific_student(user, course_key):
masquerade_group = get_masquerading_user_group(course_key, user, user_partition)
if masquerade_group is not None:
return masquerade_group
else:
audit_mode_id = settings.COURSE_ENROLLMENT_MODES.get(CourseMode.AUDIT, {}).get('id')
if course_masquerade.user_partition_id == ENROLLMENT_TRACK_PARTITION_ID:
if course_masquerade.group_id != audit_mode_id:
return cls.FULL_ACCESS
else:
return cls.LIMITED_ACCESS
# For now, treat everyone as a Full-access user, until we have the rest of the
# feature gating logic in place.
if not ContentTypeGatingConfig.enabled_for_enrollment(user=user, course_key=course_key):
return cls.FULL_ACCESS
if not ContentTypeGatingConfig.enabled_for_enrollment(user=user, course_key=course_key,
user_partition=user_partition):
return FULL_ACCESS
# If CONTENT_TYPE_GATING is enabled use the following logic to determine whether a user should have FULL_ACCESS
# or LIMITED_ACCESS
@@ -168,7 +136,7 @@ class ContentTypeGatingPartitionScheme(object):
# If there is no verified mode, all users are granted FULL_ACCESS
if not course_mode.has_verified_mode(modes_dict):
return cls.FULL_ACCESS
return FULL_ACCESS
course_enrollment = apps.get_model('student.CourseEnrollment')
@@ -188,15 +156,15 @@ class ContentTypeGatingPartitionScheme(object):
mode_slug,
course_key,
)
return cls.FULL_ACCESS
return FULL_ACCESS
if mode_slug == CourseMode.AUDIT:
return cls.LIMITED_ACCESS
return LIMITED_ACCESS
else:
return cls.FULL_ACCESS
return FULL_ACCESS
else:
# Unenrolled users don't get gated content
return cls.LIMITED_ACCESS
return LIMITED_ACCESS
@classmethod
def create_user_partition(cls, id, name, description, groups=None, parameters=None, active=True): # pylint: disable=redefined-builtin, invalid-name, unused-argument
@@ -219,8 +187,8 @@ class ContentTypeGatingPartitionScheme(object):
unicode(name),
unicode(description),
[
cls.LIMITED_ACCESS,
cls.FULL_ACCESS,
LIMITED_ACCESS,
FULL_ACCESS,
],
cls,
parameters,

View File

@@ -35,9 +35,9 @@ from lms.djangoapps.courseware.tests.factories import (
from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactory
from openedx.core.djangoapps.util.testing import TestConditionalContent
from openedx.core.lib.url_utils import quote_slashes
from openedx.features.content_type_gating.helpers import CONTENT_TYPE_GATE_GROUP_IDS
from openedx.features.content_type_gating.partitions import (
CONTENT_GATING_PARTITION_ID,
CONTENT_TYPE_GATE_GROUP_IDS,
ContentTypeGatingPartition
)
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
@@ -224,8 +224,8 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
graded=True,
group_access={
CONTENT_GATING_PARTITION_ID: [
settings.CONTENT_TYPE_GATE_GROUP_IDS['limited_access'],
settings.CONTENT_TYPE_GATE_GROUP_IDS['full_access']
CONTENT_TYPE_GATE_GROUP_IDS['limited_access'],
CONTENT_TYPE_GATE_GROUP_IDS['full_access']
]
},
)

View File

@@ -20,8 +20,8 @@ from lms.djangoapps.courseware.masquerade import (
)
from openedx.core.djangoapps.config_model_utils.models import StackedConfigurationModel
from openedx.core.djangoapps.config_model_utils.utils import is_in_holdback
from openedx.features.content_type_gating.helpers import has_staff_roles
from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID, CONTENT_TYPE_GATE_GROUP_IDS
from openedx.features.content_type_gating.helpers import CONTENT_TYPE_GATE_GROUP_IDS, has_staff_roles
from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID
from openedx.features.course_duration_limits.config import (
CONTENT_TYPE_GATING_FLAG,
FEATURE_BASED_ENROLLMENT_GLOBAL_KILL_FLAG,

View File

@@ -29,7 +29,8 @@ from lms.djangoapps.courseware.tests.factories import (
)
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID, CONTENT_TYPE_GATE_GROUP_IDS
from openedx.features.content_type_gating.helpers import CONTENT_TYPE_GATE_GROUP_IDS
from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID
from openedx.features.course_duration_limits.access import get_user_course_expiration_date, MIN_DURATION, MAX_DURATION
from openedx.features.course_duration_limits.config import EXPERIMENT_ID, EXPERIMENT_DATA_HOLDBACK_KEY
from openedx.features.course_experience.tests.views.helpers import add_course_mode