Merge pull request #14400 from open-craft/haikuginger/enterprise-course-consent
[ENT-101] Add course-specific data sharing consent hooks for Enterprise app
This commit is contained in:
@@ -2,9 +2,12 @@
|
||||
Helpers to access the enterprise app
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
import logging
|
||||
|
||||
from django.utils.http import urlencode
|
||||
|
||||
try:
|
||||
from enterprise.models import EnterpriseCustomer
|
||||
from enterprise import utils as enterprise_utils
|
||||
@@ -13,6 +16,7 @@ try:
|
||||
active_provider_enforces_data_sharing,
|
||||
get_enterprise_customer_for_request,
|
||||
)
|
||||
from enterprise.utils import consent_necessary_for_course
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
@@ -26,7 +30,32 @@ def enterprise_enabled():
|
||||
"""
|
||||
Determines whether the Enterprise app is installed
|
||||
"""
|
||||
return 'enterprise' in settings.INSTALLED_APPS
|
||||
return 'enterprise' in settings.INSTALLED_APPS and getattr(settings, 'ENABLE_ENTERPRISE_INTEGRATION', True)
|
||||
|
||||
|
||||
def consent_needed_for_course(user, course_id):
|
||||
"""
|
||||
Wrap the enterprise app check to determine if the user needs to grant
|
||||
data sharing permissions before accessing a course.
|
||||
"""
|
||||
if not enterprise_enabled():
|
||||
return False
|
||||
return consent_necessary_for_course(user, course_id)
|
||||
|
||||
|
||||
def get_course_specific_consent_url(request, course_id, return_to):
|
||||
"""
|
||||
Build a URL to redirect the user to the Enterprise app to provide data sharing
|
||||
consent for a specific course ID.
|
||||
"""
|
||||
url_params = {
|
||||
'course_id': course_id,
|
||||
'next': request.build_absolute_uri(reverse(return_to, args=(course_id,)))
|
||||
}
|
||||
querystring = urlencode(url_params)
|
||||
full_url = reverse('grant_data_sharing_permissions') + '?' + querystring
|
||||
LOGGER.info('Redirecting to %s to complete data sharing consent', full_url)
|
||||
return full_url
|
||||
|
||||
|
||||
def data_sharing_consent_requested(request):
|
||||
|
||||
@@ -188,6 +188,7 @@ class FieldOverridePerformanceTestCase(FieldOverrideTestMixin, ProceduralCourseT
|
||||
@override_settings(
|
||||
XBLOCK_FIELD_DATA_WRAPPERS=[],
|
||||
MODULESTORE_FIELD_OVERRIDE_PROVIDERS=[],
|
||||
ENABLE_ENTERPRISE_INTEGRATION=False,
|
||||
)
|
||||
def test_field_overrides(self, overrides, course_width, enable_ccx, view_as_ccx):
|
||||
"""
|
||||
|
||||
@@ -60,6 +60,31 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
|
||||
resp = self.client.get(url)
|
||||
self.assertNotIn("You are not currently enrolled in this course", resp.content)
|
||||
|
||||
@mock.patch('courseware.views.views.get_course_specific_consent_url')
|
||||
@mock.patch('courseware.views.views.consent_needed_for_course')
|
||||
def test_redirection_missing_enterprise_consent(self, mock_consent_needed, mock_get_url):
|
||||
"""
|
||||
Verify that users viewing the course info who are enrolled, but have not provided
|
||||
data sharing consent, are first redirected to a consent page, and then, once they've
|
||||
provided consent, are able to view the course info.
|
||||
"""
|
||||
self.setup_user()
|
||||
self.enroll(self.course)
|
||||
mock_consent_needed.return_value = True
|
||||
mock_get_url.return_value = reverse('dashboard')
|
||||
url = reverse('info', args=[self.course.id.to_deprecated_string()])
|
||||
|
||||
response = self.client.get(url)
|
||||
|
||||
self.assertRedirects(
|
||||
response,
|
||||
reverse('dashboard')
|
||||
)
|
||||
mock_consent_needed.assert_called_once_with(self.user, unicode(self.course.id))
|
||||
mock_consent_needed.return_value = False
|
||||
response = self.client.get(url)
|
||||
self.assertNotIn("You are not currently enrolled in this course", response.content)
|
||||
|
||||
def test_anonymous_user(self):
|
||||
url = reverse('info', args=[self.course.id.to_deprecated_string()])
|
||||
resp = self.client.get(url)
|
||||
@@ -313,7 +338,7 @@ class CourseInfoTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
|
||||
|
||||
|
||||
@attr(shard=1)
|
||||
@override_settings(FEATURES=dict(settings.FEATURES, EMBARGO=False))
|
||||
@override_settings(FEATURES=dict(settings.FEATURES, EMBARGO=False), ENABLE_ENTERPRISE_INTEGRATION=False)
|
||||
class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the info page of self-paced courses.
|
||||
|
||||
@@ -197,6 +197,29 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
)
|
||||
)
|
||||
|
||||
@patch('courseware.views.index.get_course_specific_consent_url')
|
||||
@patch('courseware.views.index.consent_needed_for_course')
|
||||
def test_redirection_missing_enterprise_consent(self, mock_consent_needed, mock_get_url):
|
||||
"""
|
||||
Verify that enrolled students are redirected to the Enterprise consent
|
||||
URL if a linked Enterprise Customer requires data sharing consent
|
||||
and it has not yet been provided.
|
||||
"""
|
||||
mock_consent_needed.return_value = True
|
||||
mock_get_url.return_value = reverse('dashboard')
|
||||
self.login(self.enrolled_user)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
'courseware',
|
||||
kwargs={'course_id': self.course.id.to_deprecated_string()}
|
||||
)
|
||||
)
|
||||
self.assertRedirects(
|
||||
response,
|
||||
reverse('dashboard')
|
||||
)
|
||||
mock_consent_needed.assert_called_once_with(self.enrolled_user, unicode(self.course.id))
|
||||
|
||||
def test_instructor_page_access_nonstaff(self):
|
||||
"""
|
||||
Verify non-staff cannot load the instructor
|
||||
|
||||
@@ -1130,6 +1130,7 @@ class StartDateTests(ModuleStoreTestCase):
|
||||
|
||||
# pylint: disable=protected-access, no-member
|
||||
@attr(shard=1)
|
||||
@override_settings(ENABLE_ENTERPRISE_INTEGRATION=False)
|
||||
@ddt.ddt
|
||||
class ProgressPageTests(ModuleStoreTestCase):
|
||||
"""
|
||||
|
||||
@@ -31,6 +31,7 @@ from shoppingcart.models import CourseRegistrationCode
|
||||
from student.models import CourseEnrollment
|
||||
from student.views import is_course_blocked
|
||||
from student.roles import GlobalStaff
|
||||
from util.enterprise_helpers import consent_needed_for_course, get_course_specific_consent_url
|
||||
from util.views import ensure_valid_course_key
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.x_module import STUDENT_VIEW
|
||||
@@ -194,6 +195,21 @@ class CoursewareIndex(View):
|
||||
self._redirect_if_needed_to_register()
|
||||
self._redirect_if_needed_for_prereqs()
|
||||
self._redirect_if_needed_for_course_survey()
|
||||
self._redirect_if_data_sharing_consent_needed()
|
||||
|
||||
def _redirect_if_data_sharing_consent_needed(self):
|
||||
"""
|
||||
Determine if the user needs to provide data sharing consent before accessing
|
||||
the course, and redirect the user to provide consent if needed.
|
||||
"""
|
||||
course_id = unicode(self.course_key)
|
||||
if consent_needed_for_course(self.real_user, course_id):
|
||||
log.warning(
|
||||
u'User %s cannot access the course %s because they have not granted consent',
|
||||
self.real_user,
|
||||
course_id,
|
||||
)
|
||||
raise Redirect(get_course_specific_consent_url(self.request, course_id, 'courseware'))
|
||||
|
||||
def _redirect_if_needed_to_pay_for_course(self):
|
||||
"""
|
||||
|
||||
@@ -86,6 +86,7 @@ from student.roles import GlobalStaff
|
||||
from util.cache import cache, cache_if_anonymous
|
||||
from util.date_utils import strftime_localized
|
||||
from util.db import outer_atomic
|
||||
from util.enterprise_helpers import consent_needed_for_course, get_course_specific_consent_url
|
||||
from util.milestones_helpers import get_prerequisite_courses_display
|
||||
from util.views import _record_feedback_in_zendesk
|
||||
from util.views import ensure_valid_course_key, ensure_valid_usage_key
|
||||
@@ -315,6 +316,11 @@ def course_info(request, course_id):
|
||||
# to access CCX redirect him to dashboard.
|
||||
return redirect(reverse('dashboard'))
|
||||
|
||||
# If the user is sponsored by an enterprise customer, and we still need to get data
|
||||
# sharing consent, redirect to do that first.
|
||||
if consent_needed_for_course(user, course_id):
|
||||
return redirect(get_course_specific_consent_url(request, course_id, 'info'))
|
||||
|
||||
# If the user needs to take an entrance exam to access this course, then we'll need
|
||||
# to send them to that specific course module before allowing them into other areas
|
||||
if user_must_complete_entrance_exam(request, user, course):
|
||||
@@ -702,6 +708,11 @@ def _progress(request, course_key, student_id):
|
||||
course = get_course_with_access(request.user, 'load', course_key, depth=None, check_if_enrolled=True)
|
||||
prep_course_for_grading(course, request)
|
||||
|
||||
# If the user is sponsored by an enterprise customer, and we still need to get data
|
||||
# sharing consent, redirect to do that first.
|
||||
if consent_needed_for_course(request.user, unicode(course.id)):
|
||||
return redirect(get_course_specific_consent_url(request, unicode(course.id), 'progress'))
|
||||
|
||||
# check to see if there is a required survey that must be taken before
|
||||
# the user can access the course.
|
||||
if survey.utils.must_answer_survey(course, request.user):
|
||||
|
||||
@@ -51,7 +51,7 @@ edx-lint==0.4.3
|
||||
astroid==1.3.8
|
||||
edx-django-oauth2-provider==1.1.4
|
||||
edx-django-sites-extensions==2.1.1
|
||||
edx-enterprise==0.19.1
|
||||
edx-enterprise==0.21.0
|
||||
edx-oauth2-provider==1.2.0
|
||||
edx-opaque-keys==0.4.0
|
||||
edx-organizations==0.4.2
|
||||
|
||||
Reference in New Issue
Block a user