Commit 3be8f92b by Andy Armstrong Committed by GitHub

Merge pull request #14790 from edx/andya/course-redirects

Add data sharing consent redirect for more course tabs
parents c6f2db35 0325425c
...@@ -22,11 +22,11 @@ from xmodule.modulestore.tests.factories import CourseFactory ...@@ -22,11 +22,11 @@ from xmodule.modulestore.tests.factories import CourseFactory
from course_modes.models import CourseMode, Mode from course_modes.models import CourseMode, Mode
from course_modes.tests.factories import CourseModeFactory from course_modes.tests.factories import CourseModeFactory
from openedx.core.djangoapps.embargo.test_utils import restrict_course from openedx.core.djangoapps.embargo.test_utils import restrict_course
from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin
from student.models import CourseEnrollment from student.models import CourseEnrollment
from student.tests.factories import CourseEnrollmentFactory, UserFactory from student.tests.factories import CourseEnrollmentFactory, UserFactory
from util.testing import UrlResetMixin from util.testing import UrlResetMixin
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
from util.tests.mixins.enterprise import EnterpriseServiceMockMixin
from util.tests.mixins.discovery import CourseCatalogServiceMockMixin from util.tests.mixins.discovery import CourseCatalogServiceMockMixin
from util import organizations_helpers as organizations_api from util import organizations_helpers as organizations_api
......
...@@ -20,14 +20,13 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey ...@@ -20,14 +20,13 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from lms.djangoapps.commerce.utils import EcommerceService from lms.djangoapps.commerce.utils import EcommerceService
from openedx.core.djangoapps.api_admin.utils import course_discovery_api_client
from course_modes.models import CourseMode from course_modes.models import CourseMode
from courseware.access import has_access from courseware.access import has_access
from edxmako.shortcuts import render_to_response from edxmako.shortcuts import render_to_response
from openedx.core.djangoapps.embargo import api as embargo_api from openedx.core.djangoapps.embargo import api as embargo_api
from openedx.features.enterprise_support import api as enterprise_api
from student.models import CourseEnrollment from student.models import CourseEnrollment
from util.db import outer_atomic from util.db import outer_atomic
from util import enterprise_helpers as enterprise_api
from util import organizations_helpers as organization_api from util import organizations_helpers as organization_api
......
...@@ -26,7 +26,7 @@ from course_modes.models import CourseMode ...@@ -26,7 +26,7 @@ from course_modes.models import CourseMode
from enrollment.views import EnrollmentUserThrottle from enrollment.views import EnrollmentUserThrottle
from util.models import RateLimitConfiguration from util.models import RateLimitConfiguration
from util.testing import UrlResetMixin from util.testing import UrlResetMixin
from util.tests.mixins.enterprise import EnterpriseServiceMockMixin from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin
from enrollment import api from enrollment import api
from enrollment.errors import CourseEnrollmentError from enrollment.errors import CourseEnrollmentError
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
......
...@@ -20,13 +20,13 @@ from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticati ...@@ -20,13 +20,13 @@ from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticati
from openedx.core.djangoapps.cors_csrf.decorators import ensure_csrf_cookie_cross_domain from openedx.core.djangoapps.cors_csrf.decorators import ensure_csrf_cookie_cross_domain
from openedx.core.djangoapps.embargo import api as embargo_api from openedx.core.djangoapps.embargo import api as embargo_api
from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in
from openedx.features.enterprise_support.api import enterprise_enabled, EnterpriseApiClient, EnterpriseApiException
from openedx.core.lib.api.authentication import ( from openedx.core.lib.api.authentication import (
SessionAuthenticationAllowInactiveUser, OAuth2AuthenticationAllowInactiveUser, SessionAuthenticationAllowInactiveUser, OAuth2AuthenticationAllowInactiveUser,
) )
from openedx.core.lib.api.permissions import ApiKeyHeaderPermission, ApiKeyHeaderPermissionIsAuthenticated from openedx.core.lib.api.permissions import ApiKeyHeaderPermission, ApiKeyHeaderPermissionIsAuthenticated
from openedx.core.lib.exceptions import CourseNotFoundError from openedx.core.lib.exceptions import CourseNotFoundError
from openedx.core.lib.log_utils import audit_log from openedx.core.lib.log_utils import audit_log
from util.enterprise_helpers import enterprise_enabled, EnterpriseApiClient, EnterpriseApiException
from enrollment import api from enrollment import api
from enrollment.errors import ( from enrollment.errors import (
CourseEnrollmentError, CourseEnrollmentError,
......
...@@ -103,7 +103,6 @@ from util.milestones_helpers import ( ...@@ -103,7 +103,6 @@ from util.milestones_helpers import (
) )
from util.password_policy_validators import validate_password_strength from util.password_policy_validators import validate_password_strength
from util.enterprise_helpers import get_dashboard_consent_notification
import third_party_auth import third_party_auth
from third_party_auth import pipeline, provider from third_party_auth import pipeline, provider
from student.helpers import ( from student.helpers import (
...@@ -117,6 +116,7 @@ from student.models import anonymous_id_for_user, UserAttribute, EnrollStatusCha ...@@ -117,6 +116,7 @@ from student.models import anonymous_id_for_user, UserAttribute, EnrollStatusCha
from shoppingcart.models import DonationConfiguration, CourseRegistrationCode from shoppingcart.models import DonationConfiguration, CourseRegistrationCode
from openedx.core.djangoapps.embargo import api as embargo_api from openedx.core.djangoapps.embargo import api as embargo_api
from openedx.features.enterprise_support.api import get_dashboard_consent_notification
import analytics import analytics
from eventtracking import tracker from eventtracking import tracker
......
...@@ -10,7 +10,7 @@ If true, it: ...@@ -10,7 +10,7 @@ If true, it:
b) calls apply_settings(), passing in the Django settings b) calls apply_settings(), passing in the Django settings
""" """
from util.enterprise_helpers import insert_enterprise_pipeline_elements from openedx.features.enterprise_support.api import insert_enterprise_pipeline_elements
_FIELDS_STORED_IN_SESSION = ['auth_entry', 'next'] _FIELDS_STORED_IN_SESSION = ['auth_entry', 'next']
_MIDDLEWARE_CLASSES = ( _MIDDLEWARE_CLASSES = (
......
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
from third_party_auth import provider, settings from third_party_auth import provider, settings
from third_party_auth.tests import testutil from third_party_auth.tests import testutil
from util.enterprise_helpers import enterprise_enabled
import unittest import unittest
from openedx.features.enterprise_support.api import enterprise_enabled
_ORIGINAL_AUTHENTICATION_BACKENDS = ('first_authentication_backend',) _ORIGINAL_AUTHENTICATION_BACKENDS = ('first_authentication_backend',)
_ORIGINAL_INSTALLED_APPS = ('first_installed_app',) _ORIGINAL_INSTALLED_APPS = ('first_installed_app',)
......
...@@ -10,7 +10,7 @@ from courseware.courses import get_course_with_access, get_course_overview_with_ ...@@ -10,7 +10,7 @@ from courseware.courses import get_course_with_access, get_course_overview_with_
from courseware.access import has_access from courseware.access import has_access
from student.models import CourseEnrollment from student.models import CourseEnrollment
from util.request import course_id_from_url from util.request import course_id_from_url
from util.enterprise_helpers import get_enterprise_consent_url from openedx.features.enterprise_support.api import get_enterprise_consent_url
class WikiAccessMiddleware(object): class WikiAccessMiddleware(object):
......
...@@ -2,7 +2,7 @@ from django.core.urlresolvers import reverse ...@@ -2,7 +2,7 @@ from django.core.urlresolvers import reverse
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from courseware.tests.tests import LoginEnrollmentTestCase from courseware.tests.tests import LoginEnrollmentTestCase
from util.tests.mixins.enterprise import EnterpriseTestConsentRequired from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
......
...@@ -16,7 +16,7 @@ from courseware.courses import get_course_by_id ...@@ -16,7 +16,7 @@ from courseware.courses import get_course_by_id
from course_wiki.utils import course_wiki_slug from course_wiki.utils import course_wiki_slug
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from util.enterprise_helpers import data_sharing_consent_required from openedx.features.enterprise_support.api import data_sharing_consent_required
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
......
...@@ -60,7 +60,7 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase): ...@@ -60,7 +60,7 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
resp = self.client.get(url) resp = self.client.get(url)
self.assertNotIn("You are not currently enrolled in this course", resp.content) self.assertNotIn("You are not currently enrolled in this course", resp.content)
@mock.patch('courseware.views.views.get_enterprise_consent_url') @mock.patch('openedx.features.enterprise_support.api.get_enterprise_consent_url')
def test_redirection_missing_enterprise_consent(self, mock_get_url): def test_redirection_missing_enterprise_consent(self, mock_get_url):
""" """
Verify that users viewing the course info who are enrolled, but have not provided Verify that users viewing the course info who are enrolled, but have not provided
......
...@@ -197,7 +197,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -197,7 +197,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
) )
) )
@patch('courseware.views.index.get_enterprise_consent_url') @patch('openedx.features.enterprise_support.api.get_enterprise_consent_url')
def test_redirection_missing_enterprise_consent(self, mock_get_url): def test_redirection_missing_enterprise_consent(self, mock_get_url):
""" """
Verify that enrolled students are redirected to the Enterprise consent Verify that enrolled students are redirected to the Enterprise consent
......
...@@ -41,21 +41,25 @@ from commerce.models import CommerceConfiguration ...@@ -41,21 +41,25 @@ from commerce.models import CommerceConfiguration
from course_modes.models import CourseMode from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory from course_modes.tests.factories import CourseModeFactory
from courseware.model_data import set_score from courseware.model_data import set_score
from courseware.module_render import toc_for_course
from courseware.testutils import RenderXBlockTestMixin from courseware.testutils import RenderXBlockTestMixin
from courseware.tests.factories import StudentModuleFactory, GlobalStaffFactory from courseware.tests.factories import StudentModuleFactory, GlobalStaffFactory
from courseware.url_helpers import get_redirect_url from courseware.url_helpers import get_redirect_url
from courseware.user_state_client import DjangoXBlockUserStateClient from courseware.user_state_client import DjangoXBlockUserStateClient
from courseware.views.index import render_accordion
from lms.djangoapps.commerce.utils import EcommerceService # pylint: disable=import-error from lms.djangoapps.commerce.utils import EcommerceService # pylint: disable=import-error
from milestones.tests.utils import MilestonesTestCaseMixin from milestones.tests.utils import MilestonesTestCaseMixin
from openedx.core.djangoapps.catalog.tests.factories import CourseFactory as CatalogCourseFactory
from openedx.core.djangoapps.catalog.tests.factories import ProgramFactory, CourseRunFactory
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.crawlers.models import CrawlersConfig from openedx.core.djangoapps.crawlers.models import CrawlersConfig
from openedx.core.djangoapps.credit.api import set_credit_requirements
from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from openedx.core.lib.gating import api as gating_api from openedx.core.lib.gating import api as gating_api
from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired
from student.models import CourseEnrollment from student.models import CourseEnrollment
from student.tests.factories import AdminFactory, UserFactory, CourseEnrollmentFactory from student.tests.factories import AdminFactory, UserFactory, CourseEnrollmentFactory
from util.tests.mixins.enterprise import EnterpriseTestConsentRequired
from util.tests.test_date_utils import fake_ugettext, fake_pgettext from util.tests.test_date_utils import fake_ugettext, fake_pgettext
from util.url import reload_django_url_config from util.url import reload_django_url_config
from util.views import ensure_valid_course_key from util.views import ensure_valid_course_key
...@@ -64,12 +68,6 @@ from xmodule.modulestore.django import modulestore ...@@ -64,12 +68,6 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_MODULESTORE from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_MODULESTORE
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls
from openedx.core.djangoapps.catalog.tests.factories import CourseFactory as CatalogCourseFactory
from openedx.core.djangoapps.catalog.tests.factories import ProgramFactory, CourseRunFactory
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
from openedx.core.djangoapps.credit.api import set_credit_requirements
from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
@attr(shard=1) @attr(shard=1)
......
...@@ -32,13 +32,12 @@ from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY ...@@ -32,13 +32,12 @@ from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
from openedx.core.djangoapps.crawlers.models import CrawlersConfig from openedx.core.djangoapps.crawlers.models import CrawlersConfig
from openedx.core.djangoapps.monitoring_utils import set_custom_metrics_for_course_key from openedx.core.djangoapps.monitoring_utils import set_custom_metrics_for_course_key
from openedx.features.enterprise_support.api import data_sharing_consent_required
from request_cache.middleware import RequestCache from request_cache.middleware import RequestCache
from shoppingcart.models import CourseRegistrationCode from shoppingcart.models import CourseRegistrationCode
from student.models import CourseEnrollment from student.models import CourseEnrollment
from student.views import is_course_blocked from student.views import is_course_blocked
from student.roles import GlobalStaff from student.roles import GlobalStaff
from survey.utils import must_answer_survey
from util.enterprise_helpers import get_enterprise_consent_url
from util.views import ensure_valid_course_key from util.views import ensure_valid_course_key
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.x_module import STUDENT_VIEW from xmodule.x_module import STUDENT_VIEW
...@@ -73,6 +72,7 @@ class CoursewareIndex(View): ...@@ -73,6 +72,7 @@ class CoursewareIndex(View):
@method_decorator(ensure_csrf_cookie) @method_decorator(ensure_csrf_cookie)
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True)) @method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True))
@method_decorator(ensure_valid_course_key) @method_decorator(ensure_valid_course_key)
@method_decorator(data_sharing_consent_required)
def get(self, request, course_id, chapter=None, section=None, position=None): def get(self, request, course_id, chapter=None, section=None, position=None):
""" """
Displays courseware accordion and associated content. If course, chapter, Displays courseware accordion and associated content. If course, chapter,
...@@ -194,22 +194,6 @@ class CoursewareIndex(View): ...@@ -194,22 +194,6 @@ class CoursewareIndex(View):
self._redirect_if_needed_to_register() self._redirect_if_needed_to_register()
self._redirect_if_needed_for_prereqs() self._redirect_if_needed_for_prereqs()
self._redirect_if_needed_for_course_survey() 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)
consent_url = get_enterprise_consent_url(self.request, course_id, user=self.real_user, return_to='courseware')
if consent_url:
log.warning(
u'User %s cannot access the course %s because they have not granted consent',
self.real_user,
course_id,
)
raise Redirect(consent_url)
def _redirect_if_needed_to_pay_for_course(self): def _redirect_if_needed_to_pay_for_course(self):
""" """
......
...@@ -89,13 +89,13 @@ from openedx.core.djangoapps.programs.utils import ProgramMarketingDataExtender ...@@ -89,13 +89,13 @@ from openedx.core.djangoapps.programs.utils import ProgramMarketingDataExtender
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from openedx.core.djangoapps.monitoring_utils import set_custom_metrics_for_course_key from openedx.core.djangoapps.monitoring_utils import set_custom_metrics_for_course_key
from openedx.features.enterprise_support.api import data_sharing_consent_required
from shoppingcart.utils import is_shopping_cart_enabled from shoppingcart.utils import is_shopping_cart_enabled
from student.models import UserTestGroup, CourseEnrollment from student.models import UserTestGroup, CourseEnrollment
from student.roles import GlobalStaff from student.roles import GlobalStaff
from util.cache import cache, cache_if_anonymous from util.cache import cache, cache_if_anonymous
from util.date_utils import strftime_localized from util.date_utils import strftime_localized
from util.db import outer_atomic from util.db import outer_atomic
from util.enterprise_helpers import get_enterprise_consent_url
from util.milestones_helpers import get_prerequisite_courses_display from util.milestones_helpers import get_prerequisite_courses_display
from util.views import _record_feedback_in_zendesk from util.views import _record_feedback_in_zendesk
from util.views import ensure_valid_course_key, ensure_valid_usage_key from util.views import ensure_valid_course_key, ensure_valid_usage_key
...@@ -107,7 +107,6 @@ from ..entrance_exams import user_can_skip_entrance_exam ...@@ -107,7 +107,6 @@ from ..entrance_exams import user_can_skip_entrance_exam
from ..module_render import get_module_for_descriptor, get_module, get_module_by_usage_id from ..module_render import get_module_for_descriptor, get_module, get_module_by_usage_id
from web_fragments.fragment import Fragment from web_fragments.fragment import Fragment
from web_fragments.views import FragmentView
log = logging.getLogger("edx.courseware") log = logging.getLogger("edx.courseware")
...@@ -291,6 +290,7 @@ def jump_to(_request, course_id, location): ...@@ -291,6 +290,7 @@ def jump_to(_request, course_id, location):
@ensure_csrf_cookie @ensure_csrf_cookie
@ensure_valid_course_key @ensure_valid_course_key
@data_sharing_consent_required
def course_info(request, course_id): def course_info(request, course_id):
""" """
Display the course's info.html, or 404 if there is no such course. Display the course's info.html, or 404 if there is no such course.
...@@ -330,12 +330,6 @@ def course_info(request, course_id): ...@@ -330,12 +330,6 @@ def course_info(request, course_id):
# to access CCX redirect him to dashboard. # to access CCX redirect him to dashboard.
return redirect(reverse('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.
consent_url = get_enterprise_consent_url(request, course_id, user=user, return_to='info')
if consent_url:
return redirect(consent_url)
# If the user needs to take an entrance exam to access this course, then we'll need # 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 # to send them to that specific course module before allowing them into other areas
if not user_can_skip_entrance_exam(user, course): if not user_can_skip_entrance_exam(user, course):
...@@ -495,6 +489,7 @@ class CourseTabView(EdxFragmentView): ...@@ -495,6 +489,7 @@ class CourseTabView(EdxFragmentView):
""" """
@method_decorator(ensure_csrf_cookie) @method_decorator(ensure_csrf_cookie)
@method_decorator(ensure_valid_course_key) @method_decorator(ensure_valid_course_key)
@method_decorator(data_sharing_consent_required)
def get(self, request, course_id, tab_type, **kwargs): def get(self, request, course_id, tab_type, **kwargs):
""" """
Displays a course tab page that contains a web fragment. Displays a course tab page that contains a web fragment.
...@@ -811,6 +806,7 @@ def program_marketing(request, program_uuid): ...@@ -811,6 +806,7 @@ def program_marketing(request, program_uuid):
@login_required @login_required
@cache_control(no_cache=True, no_store=True, must_revalidate=True) @cache_control(no_cache=True, no_store=True, must_revalidate=True)
@ensure_valid_course_key @ensure_valid_course_key
@data_sharing_consent_required
def progress(request, course_id, student_id=None): def progress(request, course_id, student_id=None):
""" Display the progress page. """ """ Display the progress page. """
course_key = CourseKey.from_string(course_id) course_key = CourseKey.from_string(course_id)
...@@ -838,12 +834,6 @@ def _progress(request, course_key, student_id): ...@@ -838,12 +834,6 @@ def _progress(request, course_key, student_id):
course = get_course_with_access(request.user, 'load', course_key, depth=None, check_if_enrolled=True) course = get_course_with_access(request.user, 'load', course_key, depth=None, check_if_enrolled=True)
prep_course_for_grading(course, request) 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.
consent_url = get_enterprise_consent_url(request, unicode(course.id), return_to='progress')
if consent_url:
return redirect(consent_url)
# check to see if there is a required survey that must be taken before # check to see if there is a required survey that must be taken before
# the user can access the course. # the user can access the course.
if survey.utils.must_answer_survey(course, request.user): if survey.utils.must_answer_survey(course, request.user):
......
...@@ -23,8 +23,8 @@ from django_comment_client.utils import strip_none ...@@ -23,8 +23,8 @@ from django_comment_client.utils import strip_none
from lms.djangoapps.discussion import views from lms.djangoapps.discussion import views
from student.tests.factories import UserFactory, CourseEnrollmentFactory from student.tests.factories import UserFactory, CourseEnrollmentFactory
from util.testing import UrlResetMixin from util.testing import UrlResetMixin
from util.tests.mixins.enterprise import EnterpriseTestConsentRequired
from openedx.core.djangoapps.util.testing import ContentGroupTestCase from openedx.core.djangoapps.util.testing import ContentGroupTestCase
from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ( from xmodule.modulestore.tests.django_utils import (
...@@ -363,14 +363,12 @@ class SingleThreadQueryCountTestCase(ForumsEnableMixin, ModuleStoreTestCase): ...@@ -363,14 +363,12 @@ class SingleThreadQueryCountTestCase(ForumsEnableMixin, ModuleStoreTestCase):
(ModuleStoreEnum.Type.split, False, 1, 3, 3, 12, 1), (ModuleStoreEnum.Type.split, False, 1, 3, 3, 12, 1),
(ModuleStoreEnum.Type.split, False, 50, 3, 3, 12, 1), (ModuleStoreEnum.Type.split, False, 50, 3, 3, 12, 1),
# Enabling Enterprise integration increases the number of (cached and uncached) SQL queries by 1, # Enabling Enterprise integration should have no effect on the number of mongo queries made.
# because the presence of the user's consent for the course must be checked. (ModuleStoreEnum.Type.mongo, True, 1, 5, 3, 13, 1),
# But there should be no effect on the number of mongo queries made. (ModuleStoreEnum.Type.mongo, True, 50, 5, 3, 13, 1),
(ModuleStoreEnum.Type.mongo, True, 1, 5, 3, 14, 2),
(ModuleStoreEnum.Type.mongo, True, 50, 5, 3, 14, 2),
# split mongo: 3 queries, regardless of thread response size. # split mongo: 3 queries, regardless of thread response size.
(ModuleStoreEnum.Type.split, True, 1, 3, 3, 13, 2), (ModuleStoreEnum.Type.split, True, 1, 3, 3, 12, 1),
(ModuleStoreEnum.Type.split, True, 50, 3, 3, 13, 2), (ModuleStoreEnum.Type.split, True, 50, 3, 3, 12, 1),
) )
@ddt.unpack @ddt.unpack
def test_number_of_mongo_queries( def test_number_of_mongo_queries(
...@@ -1616,8 +1614,6 @@ class EnterpriseConsentTestCase(EnterpriseTestConsentRequired, ForumsEnableMixin ...@@ -1616,8 +1614,6 @@ class EnterpriseConsentTestCase(EnterpriseTestConsentRequired, ForumsEnableMixin
for url in ( for url in (
reverse('discussion.views.forum_form_discussion', reverse('discussion.views.forum_form_discussion',
kwargs=dict(course_id=course_id)), kwargs=dict(course_id=course_id)),
reverse('discussion.views.inline_discussion',
kwargs=dict(course_id=course_id, discussion_id=self.discussion_id)),
reverse('discussion.views.single_thread', reverse('discussion.views.single_thread',
kwargs=dict(course_id=course_id, discussion_id=self.discussion_id, thread_id=thread_id)), kwargs=dict(course_id=course_id, discussion_id=self.discussion_id, thread_id=thread_id)),
): ):
......
...@@ -53,7 +53,6 @@ from django_comment_client.utils import ( ...@@ -53,7 +53,6 @@ from django_comment_client.utils import (
) )
import django_comment_client.utils as utils import django_comment_client.utils as utils
import lms.lib.comment_client as cc import lms.lib.comment_client as cc
from util.enterprise_helpers import data_sharing_consent_required
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
...@@ -199,7 +198,6 @@ def use_bulk_ops(view_func): ...@@ -199,7 +198,6 @@ def use_bulk_ops(view_func):
@login_required @login_required
@data_sharing_consent_required
@use_bulk_ops @use_bulk_ops
def inline_discussion(request, course_key, discussion_id): def inline_discussion(request, course_key, discussion_id):
""" """
...@@ -236,7 +234,6 @@ def inline_discussion(request, course_key, discussion_id): ...@@ -236,7 +234,6 @@ def inline_discussion(request, course_key, discussion_id):
@login_required @login_required
@data_sharing_consent_required
@use_bulk_ops @use_bulk_ops
def forum_form_discussion(request, course_key): def forum_form_discussion(request, course_key):
""" """
...@@ -277,7 +274,6 @@ def forum_form_discussion(request, course_key): ...@@ -277,7 +274,6 @@ def forum_form_discussion(request, course_key):
@require_GET @require_GET
@login_required @login_required
@data_sharing_consent_required
@use_bulk_ops @use_bulk_ops
def single_thread(request, course_key, discussion_id, thread_id): def single_thread(request, course_key, discussion_id, thread_id):
""" """
......
...@@ -34,6 +34,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_ ...@@ -34,6 +34,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_
from openedx.core.djangoapps.theming.helpers import is_request_in_themed_site from openedx.core.djangoapps.theming.helpers import is_request_in_themed_site
from openedx.core.djangoapps.user_api.accounts.api import request_password_change from openedx.core.djangoapps.user_api.accounts.api import request_password_change
from openedx.core.djangoapps.user_api.errors import UserNotFound from openedx.core.djangoapps.user_api.errors import UserNotFound
from openedx.features.enterprise_support.api import set_enterprise_branding_filter_param
from openedx.core.lib.time_zone_utils import TIME_ZONE_CHOICES from openedx.core.lib.time_zone_utils import TIME_ZONE_CHOICES
from openedx.core.lib.edx_api_utils import get_edx_api_data from openedx.core.lib.edx_api_utils import get_edx_api_data
from student.models import UserProfile from student.models import UserProfile
...@@ -47,7 +48,6 @@ from third_party_auth import pipeline ...@@ -47,7 +48,6 @@ from third_party_auth import pipeline
from third_party_auth.decorators import xframe_allow_whitelisted from third_party_auth.decorators import xframe_allow_whitelisted
from util.bad_request_rate_limiter import BadRequestRateLimiter from util.bad_request_rate_limiter import BadRequestRateLimiter
from util.date_utils import strftime_localized from util.date_utils import strftime_localized
from util.enterprise_helpers import set_enterprise_branding_filter_param
AUDIT_LOG = logging.getLogger("audit") AUDIT_LOG = logging.getLogger("audit")
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
......
...@@ -2180,6 +2180,7 @@ INSTALLED_APPS = ( ...@@ -2180,6 +2180,7 @@ INSTALLED_APPS = (
# Features # Features
'openedx.features.course_bookmarks', 'openedx.features.course_bookmarks',
'openedx.features.course_experience', 'openedx.features.course_experience',
'openedx.features.enterprise_support',
) )
######################### CSRF ######################################### ######################### CSRF #########################################
......
...@@ -14,7 +14,7 @@ from openedx.core.djangolib.markup import HTML, Text ...@@ -14,7 +14,7 @@ from openedx.core.djangolib.markup import HTML, Text
from branding import api as branding_api from branding import api as branding_api
# app that handles site status messages # app that handles site status messages
from status.status import get_site_status_msg from status.status import get_site_status_msg
from util.enterprise_helpers import get_enterprise_customer_logo_url from openedx.features.enterprise_support.api import get_enterprise_customer_logo_url
%> %>
## Provide a hook for themes to inject branding on top. ## Provide a hook for themes to inject branding on top.
......
...@@ -11,13 +11,13 @@ from django.conf.urls.static import static ...@@ -11,13 +11,13 @@ from django.conf.urls.static import static
from courseware.views.views import CourseTabView, EnrollStaffView, StaticCourseTabView from courseware.views.views import CourseTabView, EnrollStaffView, StaticCourseTabView
from config_models.views import ConfigurationModelCurrentAPIView from config_models.views import ConfigurationModelCurrentAPIView
from courseware.views.index import CoursewareIndex from courseware.views.index import CoursewareIndex
from django_comment_common.models import ForumsConfig
from openedx.core.djangoapps.auth_exchange.views import LoginWithAccessTokenView from openedx.core.djangoapps.auth_exchange.views import LoginWithAccessTokenView
from openedx.core.djangoapps.catalog.models import CatalogIntegration from openedx.core.djangoapps.catalog.models import CatalogIntegration
from openedx.core.djangoapps.programs.models import ProgramsApiConfig from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from django_comment_common.models import ForumsConfig
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from util.enterprise_helpers import enterprise_enabled from openedx.features.enterprise_support.api import enterprise_enabled
# Uncomment the next two lines to enable the admin: # Uncomment the next two lines to enable the admin:
if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'): if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'):
......
Enterprise Support
------------------
This directory contains a Django application to support usage of
enterprise features within edx-platform. The majority of the capabilities
are provided through the external edx-enterprise library that can be found
here: `https://github.com/edx/edx-enterprise`_.
""" """
Helpers to access the enterprise app APIs providing support for enterprise functionality.
""" """
from functools import wraps
import hashlib
import logging import logging
import six
from functools import wraps
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.cache import cache from django.core.cache import cache
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.template.loader import render_to_string
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from edxmako.shortcuts import render_to_string from slumber.exceptions import HttpClientError, HttpServerError
from edx_rest_api_client.client import EdxRestApiClient from edx_rest_api_client.client import EdxRestApiClient
try: try:
from enterprise import utils as enterprise_utils from enterprise import utils as enterprise_utils
...@@ -25,9 +29,6 @@ from requests.exceptions import ConnectionError, Timeout ...@@ -25,9 +29,6 @@ from requests.exceptions import ConnectionError, Timeout
from openedx.core.djangoapps.api_admin.utils import course_discovery_api_client from openedx.core.djangoapps.api_admin.utils import course_discovery_api_client
from openedx.core.lib.token_utils import JwtBuilder from openedx.core.lib.token_utils import JwtBuilder
from slumber.exceptions import HttpClientError, HttpServerError
import hashlib
import six
CONSENT_FAILED_PARAMETER = 'consent_failed' CONSENT_FAILED_PARAMETER = 'consent_failed'
...@@ -207,6 +208,12 @@ def data_sharing_consent_required(view_func): ...@@ -207,6 +208,12 @@ def data_sharing_consent_required(view_func):
# Redirect to the consent URL, if consent is required. # Redirect to the consent URL, if consent is required.
consent_url = get_enterprise_consent_url(request, course_id) consent_url = get_enterprise_consent_url(request, course_id)
if consent_url: if consent_url:
real_user = getattr(request.user, 'real_user', request.user)
LOGGER.warning(
u'User %s cannot access the course %s because they have not granted consent',
real_user,
course_id,
)
return redirect(consent_url) return redirect(consent_url)
# Otherwise, drop through to wrapped view # Otherwise, drop through to wrapped view
...@@ -439,7 +446,7 @@ def get_dashboard_consent_notification(request, user, course_enrollments): ...@@ -439,7 +446,7 @@ def get_dashboard_consent_notification(request, user, course_enrollments):
) )
return render_to_string( return render_to_string(
'util/enterprise_consent_declined_notification.html', 'enterprise_support/enterprise_consent_declined_notification.html',
{ {
'title': title, 'title': title,
'message': message, 'message': message,
......
## mako
<%page expression_filter="h"/> <%page expression_filter="h"/>
<div class="wrapper-msg urgency-info"> <div class="wrapper-msg urgency-info">
<div class="msg"> <div class="msg">
<span class="msg-icon fa fa-info-circle" aria-hidden="true"></span> <span class="msg-icon fa fa-info-circle" aria-hidden="true"></span>
......
...@@ -147,8 +147,8 @@ class EnterpriseTestConsentRequired(object): ...@@ -147,8 +147,8 @@ class EnterpriseTestConsentRequired(object):
* url: URL to test * url: URL to test
* status_code: expected status code of URL when no data sharing consent is required. * status_code: expected status code of URL when no data sharing consent is required.
""" """
with mock.patch('util.enterprise_helpers.enterprise_enabled', return_value=True): with mock.patch('openedx.features.enterprise_support.api.enterprise_enabled', return_value=True):
with mock.patch('util.enterprise_helpers.consent_necessary_for_course') as mock_consent_necessary: with mock.patch('openedx.features.enterprise_support.api.consent_necessary_for_course') as mock_consent_necessary: # pylint: disable=line-too-long
# Ensure that when consent is necessary, the user is redirected to the consent page. # Ensure that when consent is necessary, the user is redirected to the consent page.
mock_consent_necessary.return_value = True mock_consent_necessary.return_value = True
response = client.get(url) response = client.get(url)
......
""" """
Test the enterprise app helpers Test the enterprise support APIs.
""" """
import mock
import unittest import unittest
from django.conf import settings from django.conf import settings
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.test.utils import override_settings from django.test.utils import override_settings
import mock
from util.enterprise_helpers import ( from openedx.features.enterprise_support.api import (
enterprise_enabled, enterprise_enabled,
insert_enterprise_pipeline_elements, insert_enterprise_pipeline_elements,
data_sharing_consent_required, data_sharing_consent_required,
...@@ -21,9 +21,9 @@ from util.enterprise_helpers import ( ...@@ -21,9 +21,9 @@ from util.enterprise_helpers import (
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TestEnterpriseHelpers(unittest.TestCase): class TestEnterpriseApi(unittest.TestCase):
""" """
Test enterprise app helpers Test enterprise support APIs.
""" """
@override_settings(ENABLE_ENTERPRISE_INTEGRATION=False) @override_settings(ENABLE_ENTERPRISE_INTEGRATION=False)
...@@ -101,7 +101,10 @@ class TestEnterpriseHelpers(unittest.TestCase): ...@@ -101,7 +101,10 @@ class TestEnterpriseHelpers(unittest.TestCase):
self.assertEqual(logo_url, None) self.assertEqual(logo_url, None)
@override_settings(ENABLE_ENTERPRISE_INTEGRATION=True) @override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
@mock.patch('util.enterprise_helpers.get_enterprise_branding_filter_param', mock.Mock(return_value=None)) @mock.patch(
'openedx.features.enterprise_support.api.get_enterprise_branding_filter_param',
mock.Mock(return_value=None)
)
def test_get_enterprise_customer_logo_url_return_none_when_param_missing(self): def test_get_enterprise_customer_logo_url_return_none_when_param_missing(self):
""" """
Test get_enterprise_customer_logo_url return 'None' when filter parameters are missing. Test get_enterprise_customer_logo_url return 'None' when filter parameters are missing.
...@@ -142,8 +145,8 @@ class TestEnterpriseHelpers(unittest.TestCase): ...@@ -142,8 +145,8 @@ class TestEnterpriseHelpers(unittest.TestCase):
else: else:
self.assertEqual(response, (args, kwargs)) self.assertEqual(response, (args, kwargs))
@mock.patch('util.enterprise_helpers.enterprise_enabled') @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled')
@mock.patch('util.enterprise_helpers.consent_necessary_for_course') @mock.patch('openedx.features.enterprise_support.api.consent_necessary_for_course')
def test_data_consent_required_enterprise_disabled(self, def test_data_consent_required_enterprise_disabled(self,
mock_consent_necessary, mock_consent_necessary,
mock_enterprise_enabled): mock_enterprise_enabled):
...@@ -158,8 +161,8 @@ class TestEnterpriseHelpers(unittest.TestCase): ...@@ -158,8 +161,8 @@ class TestEnterpriseHelpers(unittest.TestCase):
mock_enterprise_enabled.assert_called_once() mock_enterprise_enabled.assert_called_once()
mock_consent_necessary.assert_not_called() mock_consent_necessary.assert_not_called()
@mock.patch('util.enterprise_helpers.enterprise_enabled') @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled')
@mock.patch('util.enterprise_helpers.consent_necessary_for_course') @mock.patch('openedx.features.enterprise_support.api.consent_necessary_for_course')
def test_no_course_data_consent_required(self, def test_no_course_data_consent_required(self,
mock_consent_necessary, mock_consent_necessary,
mock_enterprise_enabled): mock_enterprise_enabled):
...@@ -176,9 +179,9 @@ class TestEnterpriseHelpers(unittest.TestCase): ...@@ -176,9 +179,9 @@ class TestEnterpriseHelpers(unittest.TestCase):
mock_enterprise_enabled.assert_called_once() mock_enterprise_enabled.assert_called_once()
mock_consent_necessary.assert_called_once() mock_consent_necessary.assert_called_once()
@mock.patch('util.enterprise_helpers.enterprise_enabled') @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled')
@mock.patch('util.enterprise_helpers.consent_necessary_for_course') @mock.patch('openedx.features.enterprise_support.api.consent_necessary_for_course')
@mock.patch('util.enterprise_helpers.get_enterprise_consent_url') @mock.patch('openedx.features.enterprise_support.api.get_enterprise_consent_url')
def test_data_consent_required(self, mock_get_consent_url, mock_consent_necessary, mock_enterprise_enabled): def test_data_consent_required(self, mock_get_consent_url, mock_consent_necessary, mock_enterprise_enabled):
""" """
Verify that the wrapped function returns a redirect to the consent URL when enterprise integration is enabled, Verify that the wrapped function returns a redirect to the consent URL when enterprise integration is enabled,
...@@ -195,7 +198,7 @@ class TestEnterpriseHelpers(unittest.TestCase): ...@@ -195,7 +198,7 @@ class TestEnterpriseHelpers(unittest.TestCase):
mock_enterprise_enabled.assert_called_once() mock_enterprise_enabled.assert_called_once()
mock_consent_necessary.assert_called_once() mock_consent_necessary.assert_called_once()
@mock.patch('util.enterprise_helpers.consent_needed_for_course') @mock.patch('openedx.features.enterprise_support.api.consent_needed_for_course')
def test_get_enterprise_consent_url(self, needed_for_course_mock): def test_get_enterprise_consent_url(self, needed_for_course_mock):
""" """
Verify that get_enterprise_consent_url correctly builds URLs. Verify that get_enterprise_consent_url correctly builds URLs.
...@@ -264,7 +267,7 @@ class TestEnterpriseHelpers(unittest.TestCase): ...@@ -264,7 +267,7 @@ class TestEnterpriseHelpers(unittest.TestCase):
) )
self.assertEqual(notification_string, '') self.assertEqual(notification_string, '')
@mock.patch('util.enterprise_helpers.EnterpriseCourseEnrollment') @mock.patch('openedx.features.enterprise_support.api.EnterpriseCourseEnrollment')
def test_get_dashboard_consent_notification_no_contact_info(self, ece_mock): def test_get_dashboard_consent_notification_no_contact_info(self, ece_mock):
mock_get_ece = ece_mock.objects.get mock_get_ece = ece_mock.objects.get
ece_mock.DoesNotExist = Exception ece_mock.DoesNotExist = Exception
...@@ -300,7 +303,7 @@ class TestEnterpriseHelpers(unittest.TestCase): ...@@ -300,7 +303,7 @@ class TestEnterpriseHelpers(unittest.TestCase):
expected_header = 'Enrollment in edX Demo Course was not complete.' expected_header = 'Enrollment in edX Demo Course was not complete.'
self.assertIn(expected_header, notification_string) self.assertIn(expected_header, notification_string)
@mock.patch('util.enterprise_helpers.EnterpriseCourseEnrollment') @mock.patch('openedx.features.enterprise_support.api.EnterpriseCourseEnrollment')
def test_get_dashboard_consent_notification_contact_info(self, ece_mock): def test_get_dashboard_consent_notification_contact_info(self, ece_mock):
mock_get_ece = ece_mock.objects.get mock_get_ece = ece_mock.objects.get
ece_mock.DoesNotExist = Exception ece_mock.DoesNotExist = Exception
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment