Merge pull request #30741 from openedx/hammad/ENT-5992
feat: added support to check for DSC in course-home:course-metadata view.
This commit is contained in:
@@ -99,32 +99,51 @@ class CourseHomeMetadataTests(BaseCourseHomeTests):
|
||||
'enroll_user': True,
|
||||
'instructor_role': False,
|
||||
'masquerade_role': None,
|
||||
'dsc_required': False,
|
||||
'expect_course_access': True,
|
||||
'error_code': None,
|
||||
},
|
||||
{
|
||||
# Un-enrolled learners should NOT have access.
|
||||
'enroll_user': False,
|
||||
'instructor_role': False,
|
||||
'masquerade_role': None,
|
||||
'dsc_required': False,
|
||||
'expect_course_access': False,
|
||||
'error_code': 'enrollment_required'
|
||||
},
|
||||
{
|
||||
# Un-enrolled instructors should have access.
|
||||
'enroll_user': False,
|
||||
'instructor_role': True,
|
||||
'masquerade_role': None,
|
||||
'dsc_required': False,
|
||||
'expect_course_access': True,
|
||||
'error_code': None
|
||||
},
|
||||
{
|
||||
# Un-enrolled instructors masquerading as students should have access.
|
||||
'enroll_user': False,
|
||||
'instructor_role': True,
|
||||
'masquerade_role': 'student',
|
||||
'dsc_required': False,
|
||||
'expect_course_access': True,
|
||||
'error_code': None
|
||||
},
|
||||
{
|
||||
# Data sharing Consent required learners should Not have access.
|
||||
'enroll_user': True,
|
||||
'instructor_role': False,
|
||||
'masquerade_role': None,
|
||||
'dsc_required': True,
|
||||
'expect_course_access': False,
|
||||
'error_code': 'data_sharing_access_required'
|
||||
}
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_course_access(self, enroll_user, instructor_role, masquerade_role, expect_course_access):
|
||||
def test_course_access(
|
||||
self, enroll_user, instructor_role, masquerade_role, dsc_required, expect_course_access, error_code
|
||||
):
|
||||
"""
|
||||
Test that course_access is calculated correctly based on
|
||||
access to MFE and access to the course itself.
|
||||
@@ -136,13 +155,12 @@ class CourseHomeMetadataTests(BaseCourseHomeTests):
|
||||
if masquerade_role:
|
||||
self.update_masquerade(role=masquerade_role)
|
||||
|
||||
response = self.client.get(self.url)
|
||||
consent_url = 'dump/consent/url' if dsc_required else None
|
||||
with mock.patch('openedx.features.enterprise_support.api.get_enterprise_consent_url', return_value=consent_url):
|
||||
response = self.client.get(self.url)
|
||||
|
||||
assert response.status_code == 200
|
||||
if expect_course_access:
|
||||
assert response.data['course_access']['has_access']
|
||||
else:
|
||||
assert not response.data['course_access']['has_access']
|
||||
|
||||
assert response.data['course_access']['has_access'] == expect_course_access
|
||||
assert response.data['course_access']['error_code'] == error_code
|
||||
# Start date is used when handling some errors, so make sure it is present too
|
||||
assert response.data['start'] == self.course.start.isoformat() + 'Z'
|
||||
|
||||
@@ -88,6 +88,7 @@ class CourseHomeMetadataView(RetrieveAPIView):
|
||||
'load',
|
||||
check_if_enrolled=True,
|
||||
check_if_authenticated=True,
|
||||
check_if_dsc_required=True,
|
||||
)
|
||||
|
||||
_, request.user = setup_masquerade(
|
||||
|
||||
@@ -227,6 +227,17 @@ class EnrollmentRequiredAccessError(AccessError):
|
||||
super().__init__(error_code, developer_message, user_message)
|
||||
|
||||
|
||||
class DataSharingConsentRequiredAccessError(AccessError):
|
||||
"""
|
||||
Access denied because the user must give Data sharing consent before access it.
|
||||
"""
|
||||
def __init__(self, consent_url):
|
||||
error_code = "data_sharing_access_required"
|
||||
developer_message = consent_url
|
||||
user_message = _("You must give Data Sharing Consent for the course")
|
||||
super().__init__(error_code, developer_message, user_message)
|
||||
|
||||
|
||||
class AuthenticationRequiredAccessError(AccessError):
|
||||
"""
|
||||
Access denied because the user must be authenticated to see it
|
||||
|
||||
@@ -7,25 +7,23 @@ It allows us to share code between access.py and block transformers.
|
||||
from datetime import datetime, timedelta
|
||||
from logging import getLogger
|
||||
|
||||
from crum import get_current_request
|
||||
from django.conf import settings
|
||||
from pytz import UTC
|
||||
from lms.djangoapps.courseware.access_response import (
|
||||
AccessResponse,
|
||||
StartDateError,
|
||||
EnrollmentRequiredAccessError,
|
||||
AuthenticationRequiredAccessError,
|
||||
)
|
||||
from lms.djangoapps.courseware.masquerade import get_course_masquerade, is_masquerading_as_student
|
||||
from openedx.core.djangoapps.util.user_messages import PageLevelMessages # lint-amnesty, pylint: disable=unused-import
|
||||
from openedx.core.djangolib.markup import HTML # lint-amnesty, pylint: disable=unused-import
|
||||
from openedx.features.course_experience import (
|
||||
COURSE_PRE_START_ACCESS_FLAG,
|
||||
COURSE_ENABLE_UNENROLLED_ACCESS_FLAG,
|
||||
)
|
||||
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.roles import CourseBetaTesterRole
|
||||
from xmodule.util.xmodule_django import get_current_request_hostname # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from lms.djangoapps.courseware.access_response import (
|
||||
AccessResponse,
|
||||
AuthenticationRequiredAccessError,
|
||||
DataSharingConsentRequiredAccessError,
|
||||
EnrollmentRequiredAccessError,
|
||||
StartDateError
|
||||
)
|
||||
from lms.djangoapps.courseware.masquerade import get_course_masquerade, is_masquerading_as_student
|
||||
from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, COURSE_PRE_START_ACCESS_FLAG
|
||||
from xmodule.course_module import COURSE_VISIBILITY_PUBLIC # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.util.xmodule_django import get_current_request_hostname # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
DEBUG_ACCESS = False
|
||||
log = getLogger(__name__)
|
||||
@@ -168,3 +166,23 @@ def check_public_access(course, visibilities):
|
||||
return ACCESS_GRANTED
|
||||
|
||||
return ACCESS_DENIED
|
||||
|
||||
|
||||
def check_data_sharing_consent(course_id):
|
||||
"""
|
||||
Grants access if the user is do not need DataSharing consent, otherwise returns data sharing link.
|
||||
|
||||
Returns:
|
||||
AccessResponse: Either ACCESS_GRANTED or DataSharingConsentRequiredAccessError
|
||||
"""
|
||||
from openedx.features.enterprise_support.api import get_enterprise_consent_url
|
||||
consent_url = get_enterprise_consent_url(
|
||||
request=get_current_request(),
|
||||
course_id=course_id,
|
||||
return_to='courseware',
|
||||
enrollment_exists=True,
|
||||
source='CoursewareAccess'
|
||||
)
|
||||
if consent_url:
|
||||
return DataSharingConsentRequiredAccessError(consent_url=consent_url)
|
||||
return ACCESS_GRANTED
|
||||
|
||||
@@ -19,17 +19,21 @@ from fs.errors import ResourceNotFound
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
from path import Path as path
|
||||
|
||||
from openedx.core.lib.cache_utils import request_cached
|
||||
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_string
|
||||
from common.djangoapps.static_replace import replace_static_urls
|
||||
from common.djangoapps.util.date_utils import strftime_localized
|
||||
from lms.djangoapps import branding
|
||||
from lms.djangoapps.course_blocks.api import get_course_blocks
|
||||
from lms.djangoapps.courseware.access import has_access
|
||||
from lms.djangoapps.courseware.access_response import (
|
||||
AuthenticationRequiredAccessError,
|
||||
EnrollmentRequiredAccessError,
|
||||
MilestoneAccessError,
|
||||
OldMongoAccessError,
|
||||
StartDateError,
|
||||
StartDateError
|
||||
)
|
||||
from lms.djangoapps.courseware.access_utils import check_authentication, check_data_sharing_consent, check_enrollment
|
||||
from lms.djangoapps.courseware.courseware_access_exception import CoursewareAccessException
|
||||
from lms.djangoapps.courseware.date_summary import (
|
||||
CertificateAvailableDate,
|
||||
CourseAssignmentDate,
|
||||
@@ -40,29 +44,20 @@ from lms.djangoapps.courseware.date_summary import (
|
||||
VerificationDeadlineDate,
|
||||
VerifiedUpgradeDeadlineDate
|
||||
)
|
||||
from lms.djangoapps.courseware.exceptions import CourseRunNotFound
|
||||
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, CourseRunNotFound
|
||||
from lms.djangoapps.courseware.masquerade import check_content_start_date_for_masquerade_user
|
||||
from lms.djangoapps.courseware.model_data import FieldDataCache
|
||||
from lms.djangoapps.courseware.module_render import get_module
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_string
|
||||
from lms.djangoapps.courseware.access_utils import (
|
||||
check_authentication,
|
||||
check_enrollment,
|
||||
)
|
||||
from lms.djangoapps.courseware.courseware_access_exception import CoursewareAccessException
|
||||
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect
|
||||
from lms.djangoapps.course_blocks.api import get_course_blocks
|
||||
from lms.djangoapps.survey.utils import SurveyRequiredAccessError, check_survey_required_and_unanswered
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.enrollments.api import get_course_enrollment_details
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.lib.api.view_utils import LazySequence
|
||||
from openedx.core.lib.cache_utils import request_cached
|
||||
from openedx.core.lib.courses import get_course_by_id
|
||||
from openedx.features.course_duration_limits.access import AuditExpiredError
|
||||
from openedx.features.course_experience import RELATIVE_DATES_FLAG
|
||||
from openedx.features.course_experience.utils import is_block_structure_complete_for_assignments
|
||||
from common.djangoapps.static_replace import replace_static_urls
|
||||
from lms.djangoapps.survey.utils import SurveyRequiredAccessError, check_survey_required_and_unanswered
|
||||
from common.djangoapps.util.date_utils import strftime_localized
|
||||
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.x_module import STUDENT_VIEW # lint-amnesty, pylint: disable=wrong-import-order
|
||||
@@ -135,7 +130,15 @@ def get_course_overview_with_access(user, action, course_key, check_if_enrolled=
|
||||
return course_overview
|
||||
|
||||
|
||||
def check_course_access(course, user, action, check_if_enrolled=False, check_survey_complete=True, check_if_authenticated=False): # lint-amnesty, pylint: disable=line-too-long
|
||||
def check_course_access(
|
||||
course,
|
||||
user,
|
||||
action,
|
||||
check_if_enrolled=False,
|
||||
check_survey_complete=True,
|
||||
check_if_authenticated=False,
|
||||
check_if_dsc_required=False,
|
||||
):
|
||||
"""
|
||||
Check that the user has the access to perform the specified action
|
||||
on the course (CourseBlock|CourseOverview).
|
||||
@@ -161,6 +164,11 @@ def check_course_access(course, user, action, check_if_enrolled=False, check_sur
|
||||
if not enrollment_access_response:
|
||||
return enrollment_access_response
|
||||
|
||||
if check_if_dsc_required:
|
||||
data_sharing_consent_response = check_data_sharing_consent(course)
|
||||
if not data_sharing_consent_response:
|
||||
return data_sharing_consent_response
|
||||
|
||||
# Redirect if the user must answer a survey before entering the course.
|
||||
if check_survey_complete and action == 'load':
|
||||
survey_access_response = check_survey_required_and_unanswered(user, course)
|
||||
|
||||
Reference in New Issue
Block a user