From 0325425c8c9a6b60f60f02be4df5557f776a5764 Mon Sep 17 00:00:00 2001 From: Andy Armstrong Date: Wed, 29 Mar 2017 15:31:48 -0400 Subject: [PATCH] Add data sharing consent redirect for more course tabs LEARNER-394 --- .../course_modes/tests/test_views.py | 2 +- common/djangoapps/course_modes/views.py | 3 +- .../djangoapps/enrollment/tests/test_views.py | 2 +- common/djangoapps/enrollment/views.py | 2 +- common/djangoapps/student/views.py | 2 +- .../djangoapps/third_party_auth/settings.py | 2 +- .../third_party_auth/tests/test_settings.py | 3 +- lms/djangoapps/course_wiki/middleware.py | 2 +- lms/djangoapps/course_wiki/tests/tests.py | 2 +- lms/djangoapps/course_wiki/views.py | 2 +- .../courseware/tests/test_course_info.py | 2 +- .../tests/test_view_authentication.py | 2 +- lms/djangoapps/courseware/tests/test_views.py | 16 ++++----- lms/djangoapps/courseware/views/index.py | 20 ++--------- lms/djangoapps/courseware/views/views.py | 18 +++------- lms/djangoapps/discussion/tests/test_views.py | 16 ++++----- lms/djangoapps/discussion/views.py | 4 --- lms/djangoapps/student_account/views.py | 2 +- lms/envs/common.py | 1 + lms/templates/navigation.html | 2 +- lms/urls.py | 4 +-- .../features/enterprise_support/README.rst | 7 ++++ .../features/enterprise_support/__init__.py | 0 .../features/enterprise_support/api.py | 23 +++++++----- ...erprise_consent_declined_notification.html | 3 ++ .../enterprise_support/tests/__init__.py | 0 .../tests/mixins/__init__.py | 0 .../tests/mixins/enterprise.py | 4 +-- .../enterprise_support/tests/test_api.py | 35 ++++++++++--------- 29 files changed, 83 insertions(+), 98 deletions(-) create mode 100644 openedx/features/enterprise_support/README.rst create mode 100644 openedx/features/enterprise_support/__init__.py rename common/djangoapps/util/enterprise_helpers.py => openedx/features/enterprise_support/api.py (97%) rename {common/templates/util => openedx/features/enterprise_support/templates/enterprise_support}/enterprise_consent_declined_notification.html (97%) create mode 100644 openedx/features/enterprise_support/tests/__init__.py create mode 100644 openedx/features/enterprise_support/tests/mixins/__init__.py rename {common/djangoapps/util => openedx/features/enterprise_support}/tests/mixins/enterprise.py (95%) rename common/djangoapps/util/tests/test_enterprise_helpers.py => openedx/features/enterprise_support/tests/test_api.py (92%) diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py index cdddaa1299..c46db18ecc 100644 --- a/common/djangoapps/course_modes/tests/test_views.py +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -22,11 +22,11 @@ from xmodule.modulestore.tests.factories import CourseFactory from course_modes.models import CourseMode, Mode from course_modes.tests.factories import CourseModeFactory 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.tests.factories import CourseEnrollmentFactory, UserFactory from util.testing import UrlResetMixin 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 import organizations_helpers as organizations_api diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 7edb9817f2..637103730d 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -20,14 +20,13 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey from xmodule.modulestore.django import modulestore 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 courseware.access import has_access from edxmako.shortcuts import render_to_response 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 util.db import outer_atomic -from util import enterprise_helpers as enterprise_api from util import organizations_helpers as organization_api diff --git a/common/djangoapps/enrollment/tests/test_views.py b/common/djangoapps/enrollment/tests/test_views.py index 7945ae511d..6a623810ae 100644 --- a/common/djangoapps/enrollment/tests/test_views.py +++ b/common/djangoapps/enrollment/tests/test_views.py @@ -26,7 +26,7 @@ from course_modes.models import CourseMode from enrollment.views import EnrollmentUserThrottle from util.models import RateLimitConfiguration 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.errors import CourseEnrollmentError from openedx.core.djangoapps.content.course_overviews.models import CourseOverview diff --git a/common/djangoapps/enrollment/views.py b/common/djangoapps/enrollment/views.py index a4d623361e..9996cbd4ba 100644 --- a/common/djangoapps/enrollment/views.py +++ b/common/djangoapps/enrollment/views.py @@ -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.embargo import api as embargo_api 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 ( SessionAuthenticationAllowInactiveUser, OAuth2AuthenticationAllowInactiveUser, ) from openedx.core.lib.api.permissions import ApiKeyHeaderPermission, ApiKeyHeaderPermissionIsAuthenticated from openedx.core.lib.exceptions import CourseNotFoundError from openedx.core.lib.log_utils import audit_log -from util.enterprise_helpers import enterprise_enabled, EnterpriseApiClient, EnterpriseApiException from enrollment import api from enrollment.errors import ( CourseEnrollmentError, diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 571386da59..8572193178 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -103,7 +103,6 @@ from util.milestones_helpers import ( ) from util.password_policy_validators import validate_password_strength -from util.enterprise_helpers import get_dashboard_consent_notification import third_party_auth from third_party_auth import pipeline, provider from student.helpers import ( @@ -117,6 +116,7 @@ from student.models import anonymous_id_for_user, UserAttribute, EnrollStatusCha from shoppingcart.models import DonationConfiguration, CourseRegistrationCode from openedx.core.djangoapps.embargo import api as embargo_api +from openedx.features.enterprise_support.api import get_dashboard_consent_notification import analytics from eventtracking import tracker diff --git a/common/djangoapps/third_party_auth/settings.py b/common/djangoapps/third_party_auth/settings.py index 9a4c3ab1dc..547a658853 100644 --- a/common/djangoapps/third_party_auth/settings.py +++ b/common/djangoapps/third_party_auth/settings.py @@ -10,7 +10,7 @@ If true, it: 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'] _MIDDLEWARE_CLASSES = ( diff --git a/common/djangoapps/third_party_auth/tests/test_settings.py b/common/djangoapps/third_party_auth/tests/test_settings.py index ae83e69622..09c02583b5 100644 --- a/common/djangoapps/third_party_auth/tests/test_settings.py +++ b/common/djangoapps/third_party_auth/tests/test_settings.py @@ -2,9 +2,10 @@ from third_party_auth import provider, settings from third_party_auth.tests import testutil -from util.enterprise_helpers import enterprise_enabled import unittest +from openedx.features.enterprise_support.api import enterprise_enabled + _ORIGINAL_AUTHENTICATION_BACKENDS = ('first_authentication_backend',) _ORIGINAL_INSTALLED_APPS = ('first_installed_app',) diff --git a/lms/djangoapps/course_wiki/middleware.py b/lms/djangoapps/course_wiki/middleware.py index a7e3827f9b..a939408c48 100644 --- a/lms/djangoapps/course_wiki/middleware.py +++ b/lms/djangoapps/course_wiki/middleware.py @@ -10,7 +10,7 @@ from courseware.courses import get_course_with_access, get_course_overview_with_ from courseware.access import has_access from student.models import CourseEnrollment 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): diff --git a/lms/djangoapps/course_wiki/tests/tests.py b/lms/djangoapps/course_wiki/tests/tests.py index 85305ecb04..2ea09a0edf 100644 --- a/lms/djangoapps/course_wiki/tests/tests.py +++ b/lms/djangoapps/course_wiki/tests/tests.py @@ -2,7 +2,7 @@ from django.core.urlresolvers import reverse from nose.plugins.attrib import attr 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.factories import CourseFactory diff --git a/lms/djangoapps/course_wiki/views.py b/lms/djangoapps/course_wiki/views.py index f14eaa4daf..89bb3d1c0f 100644 --- a/lms/djangoapps/course_wiki/views.py +++ b/lms/djangoapps/course_wiki/views.py @@ -16,7 +16,7 @@ from courseware.courses import get_course_by_id from course_wiki.utils import course_wiki_slug from opaque_keys.edx.locations import SlashSeparatedCourseKey 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__) diff --git a/lms/djangoapps/courseware/tests/test_course_info.py b/lms/djangoapps/courseware/tests/test_course_info.py index b7dbdd7541..9de1a6e56b 100644 --- a/lms/djangoapps/courseware/tests/test_course_info.py +++ b/lms/djangoapps/courseware/tests/test_course_info.py @@ -60,7 +60,7 @@ 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_enterprise_consent_url') + @mock.patch('openedx.features.enterprise_support.api.get_enterprise_consent_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 diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index 38d69e85a0..eac6e4ec3e 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -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): """ Verify that enrolled students are redirected to the Enterprise consent diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index a0d2b05525..cfb5d0a4b6 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -41,21 +41,25 @@ from commerce.models import CommerceConfiguration from course_modes.models import CourseMode from course_modes.tests.factories import CourseModeFactory from courseware.model_data import set_score -from courseware.module_render import toc_for_course from courseware.testutils import RenderXBlockTestMixin from courseware.tests.factories import StudentModuleFactory, GlobalStaffFactory from courseware.url_helpers import get_redirect_url 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 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.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.lib.gating import api as gating_api +from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired from student.models import CourseEnrollment 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.url import reload_django_url_config from util.views import ensure_valid_course_key @@ -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 ModuleStoreTestCase, SharedModuleStoreTestCase 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) diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py index 7fae4a491a..aaf3036d90 100644 --- a/lms/djangoapps/courseware/views/index.py +++ b/lms/djangoapps/courseware/views/index.py @@ -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.crawlers.models import CrawlersConfig 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 shoppingcart.models import CourseRegistrationCode from student.models import CourseEnrollment from student.views import is_course_blocked 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 xmodule.modulestore.django import modulestore from xmodule.x_module import STUDENT_VIEW @@ -73,6 +72,7 @@ class CoursewareIndex(View): @method_decorator(ensure_csrf_cookie) @method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True)) @method_decorator(ensure_valid_course_key) + @method_decorator(data_sharing_consent_required) def get(self, request, course_id, chapter=None, section=None, position=None): """ Displays courseware accordion and associated content. If course, chapter, @@ -194,22 +194,6 @@ 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) - 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): """ diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index ff7f2d9ced..30ac4c1e80 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -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.self_paced.models import SelfPacedConfiguration 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 student.models import UserTestGroup, CourseEnrollment 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 get_enterprise_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 @@ -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 web_fragments.fragment import Fragment -from web_fragments.views import FragmentView log = logging.getLogger("edx.courseware") @@ -291,6 +290,7 @@ def jump_to(_request, course_id, location): @ensure_csrf_cookie @ensure_valid_course_key +@data_sharing_consent_required def course_info(request, course_id): """ Display the course's info.html, or 404 if there is no such course. @@ -330,12 +330,6 @@ 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. - 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 # to send them to that specific course module before allowing them into other areas if not user_can_skip_entrance_exam(user, course): @@ -495,6 +489,7 @@ class CourseTabView(EdxFragmentView): """ @method_decorator(ensure_csrf_cookie) @method_decorator(ensure_valid_course_key) + @method_decorator(data_sharing_consent_required) def get(self, request, course_id, tab_type, **kwargs): """ Displays a course tab page that contains a web fragment. @@ -811,6 +806,7 @@ def program_marketing(request, program_uuid): @login_required @cache_control(no_cache=True, no_store=True, must_revalidate=True) @ensure_valid_course_key +@data_sharing_consent_required def progress(request, course_id, student_id=None): """ Display the progress page. """ course_key = CourseKey.from_string(course_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) 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 # the user can access the course. if survey.utils.must_answer_survey(course, request.user): diff --git a/lms/djangoapps/discussion/tests/test_views.py b/lms/djangoapps/discussion/tests/test_views.py index 518ddb6a47..534a8c3e49 100644 --- a/lms/djangoapps/discussion/tests/test_views.py +++ b/lms/djangoapps/discussion/tests/test_views.py @@ -23,8 +23,8 @@ from django_comment_client.utils import strip_none from lms.djangoapps.discussion import views from student.tests.factories import UserFactory, CourseEnrollmentFactory from util.testing import UrlResetMixin -from util.tests.mixins.enterprise import EnterpriseTestConsentRequired 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.django import modulestore from xmodule.modulestore.tests.django_utils import ( @@ -363,14 +363,12 @@ class SingleThreadQueryCountTestCase(ForumsEnableMixin, ModuleStoreTestCase): (ModuleStoreEnum.Type.split, False, 1, 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, - # because the presence of the user's consent for the course must be checked. - # But there should be no effect on the number of mongo queries made. - (ModuleStoreEnum.Type.mongo, True, 1, 5, 3, 14, 2), - (ModuleStoreEnum.Type.mongo, True, 50, 5, 3, 14, 2), + # Enabling Enterprise integration should have no effect on the number of mongo queries made. + (ModuleStoreEnum.Type.mongo, True, 1, 5, 3, 13, 1), + (ModuleStoreEnum.Type.mongo, True, 50, 5, 3, 13, 1), # split mongo: 3 queries, regardless of thread response size. - (ModuleStoreEnum.Type.split, True, 1, 3, 3, 13, 2), - (ModuleStoreEnum.Type.split, True, 50, 3, 3, 13, 2), + (ModuleStoreEnum.Type.split, True, 1, 3, 3, 12, 1), + (ModuleStoreEnum.Type.split, True, 50, 3, 3, 12, 1), ) @ddt.unpack def test_number_of_mongo_queries( @@ -1616,8 +1614,6 @@ class EnterpriseConsentTestCase(EnterpriseTestConsentRequired, ForumsEnableMixin for url in ( reverse('discussion.views.forum_form_discussion', 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', kwargs=dict(course_id=course_id, discussion_id=self.discussion_id, thread_id=thread_id)), ): diff --git a/lms/djangoapps/discussion/views.py b/lms/djangoapps/discussion/views.py index 11c6d5aa6d..6822503a91 100644 --- a/lms/djangoapps/discussion/views.py +++ b/lms/djangoapps/discussion/views.py @@ -53,7 +53,6 @@ from django_comment_client.utils import ( ) import django_comment_client.utils as utils import lms.lib.comment_client as cc -from util.enterprise_helpers import data_sharing_consent_required from opaque_keys.edx.keys import CourseKey @@ -199,7 +198,6 @@ def use_bulk_ops(view_func): @login_required -@data_sharing_consent_required @use_bulk_ops def inline_discussion(request, course_key, discussion_id): """ @@ -236,7 +234,6 @@ def inline_discussion(request, course_key, discussion_id): @login_required -@data_sharing_consent_required @use_bulk_ops def forum_form_discussion(request, course_key): """ @@ -277,7 +274,6 @@ def forum_form_discussion(request, course_key): @require_GET @login_required -@data_sharing_consent_required @use_bulk_ops def single_thread(request, course_key, discussion_id, thread_id): """ diff --git a/lms/djangoapps/student_account/views.py b/lms/djangoapps/student_account/views.py index 9a355f3b55..1e6b46b822 100644 --- a/lms/djangoapps/student_account/views.py +++ b/lms/djangoapps/student_account/views.py @@ -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.user_api.accounts.api import request_password_change 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.edx_api_utils import get_edx_api_data from student.models import UserProfile @@ -47,7 +48,6 @@ from third_party_auth import pipeline from third_party_auth.decorators import xframe_allow_whitelisted from util.bad_request_rate_limiter import BadRequestRateLimiter from util.date_utils import strftime_localized -from util.enterprise_helpers import set_enterprise_branding_filter_param AUDIT_LOG = logging.getLogger("audit") log = logging.getLogger(__name__) diff --git a/lms/envs/common.py b/lms/envs/common.py index bb0fc9a00b..8a9c2f4c78 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -2180,6 +2180,7 @@ INSTALLED_APPS = ( # Features 'openedx.features.course_bookmarks', 'openedx.features.course_experience', + 'openedx.features.enterprise_support', ) ######################### CSRF ######################################### diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index ba90395a16..bdf8057c45 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -14,7 +14,7 @@ from openedx.core.djangolib.markup import HTML, Text from branding import api as branding_api # app that handles site status messages 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. diff --git a/lms/urls.py b/lms/urls.py index 1974bcf6cf..babf8f2be3 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -11,13 +11,13 @@ from django.conf.urls.static import static from courseware.views.views import CourseTabView, EnrollStaffView, StaticCourseTabView from config_models.views import ConfigurationModelCurrentAPIView 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.catalog.models import CatalogIntegration from openedx.core.djangoapps.programs.models import ProgramsApiConfig 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 util.enterprise_helpers import enterprise_enabled +from openedx.features.enterprise_support.api import enterprise_enabled # Uncomment the next two lines to enable the admin: if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'): diff --git a/openedx/features/enterprise_support/README.rst b/openedx/features/enterprise_support/README.rst new file mode 100644 index 0000000000..0a34a6ac64 --- /dev/null +++ b/openedx/features/enterprise_support/README.rst @@ -0,0 +1,7 @@ +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`_. diff --git a/openedx/features/enterprise_support/__init__.py b/openedx/features/enterprise_support/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/djangoapps/util/enterprise_helpers.py b/openedx/features/enterprise_support/api.py similarity index 97% rename from common/djangoapps/util/enterprise_helpers.py rename to openedx/features/enterprise_support/api.py index 248095bd5c..7160babfa4 100644 --- a/common/djangoapps/util/enterprise_helpers.py +++ b/openedx/features/enterprise_support/api.py @@ -1,17 +1,21 @@ """ -Helpers to access the enterprise app +APIs providing support for enterprise functionality. """ -import logging - from functools import wraps +import hashlib +import logging +import six + from django.conf import settings from django.contrib.auth.models import User from django.core.cache import cache from django.core.urlresolvers import reverse from django.shortcuts import redirect +from django.template.loader import render_to_string from django.utils.http import urlencode 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 try: from enterprise import utils as enterprise_utils @@ -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.lib.token_utils import JwtBuilder -from slumber.exceptions import HttpClientError, HttpServerError -import hashlib -import six CONSENT_FAILED_PARAMETER = 'consent_failed' @@ -207,6 +208,12 @@ def data_sharing_consent_required(view_func): # Redirect to the consent URL, if consent is required. consent_url = get_enterprise_consent_url(request, course_id) 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) # Otherwise, drop through to wrapped view @@ -439,7 +446,7 @@ def get_dashboard_consent_notification(request, user, course_enrollments): ) return render_to_string( - 'util/enterprise_consent_declined_notification.html', + 'enterprise_support/enterprise_consent_declined_notification.html', { 'title': title, 'message': message, diff --git a/common/templates/util/enterprise_consent_declined_notification.html b/openedx/features/enterprise_support/templates/enterprise_support/enterprise_consent_declined_notification.html similarity index 97% rename from common/templates/util/enterprise_consent_declined_notification.html rename to openedx/features/enterprise_support/templates/enterprise_support/enterprise_consent_declined_notification.html index e7a0f4bdb6..a9b837a26d 100644 --- a/common/templates/util/enterprise_consent_declined_notification.html +++ b/openedx/features/enterprise_support/templates/enterprise_support/enterprise_consent_declined_notification.html @@ -1,4 +1,7 @@ +## mako + <%page expression_filter="h"/> +
diff --git a/openedx/features/enterprise_support/tests/__init__.py b/openedx/features/enterprise_support/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openedx/features/enterprise_support/tests/mixins/__init__.py b/openedx/features/enterprise_support/tests/mixins/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/djangoapps/util/tests/mixins/enterprise.py b/openedx/features/enterprise_support/tests/mixins/enterprise.py similarity index 95% rename from common/djangoapps/util/tests/mixins/enterprise.py rename to openedx/features/enterprise_support/tests/mixins/enterprise.py index 7dcfc88363..2df604186d 100644 --- a/common/djangoapps/util/tests/mixins/enterprise.py +++ b/openedx/features/enterprise_support/tests/mixins/enterprise.py @@ -147,8 +147,8 @@ class EnterpriseTestConsentRequired(object): * url: URL to test * 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('util.enterprise_helpers.consent_necessary_for_course') as mock_consent_necessary: + with mock.patch('openedx.features.enterprise_support.api.enterprise_enabled', return_value=True): + 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. mock_consent_necessary.return_value = True response = client.get(url) diff --git a/common/djangoapps/util/tests/test_enterprise_helpers.py b/openedx/features/enterprise_support/tests/test_api.py similarity index 92% rename from common/djangoapps/util/tests/test_enterprise_helpers.py rename to openedx/features/enterprise_support/tests/test_api.py index 333a34768b..672ab15aab 100644 --- a/common/djangoapps/util/tests/test_enterprise_helpers.py +++ b/openedx/features/enterprise_support/tests/test_api.py @@ -1,14 +1,14 @@ """ -Test the enterprise app helpers +Test the enterprise support APIs. """ +import mock import unittest from django.conf import settings from django.http import HttpResponseRedirect from django.test.utils import override_settings -import mock -from util.enterprise_helpers import ( +from openedx.features.enterprise_support.api import ( enterprise_enabled, insert_enterprise_pipeline_elements, data_sharing_consent_required, @@ -21,9 +21,9 @@ from util.enterprise_helpers import ( @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) @@ -101,7 +101,10 @@ class TestEnterpriseHelpers(unittest.TestCase): self.assertEqual(logo_url, None) @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): """ Test get_enterprise_customer_logo_url return 'None' when filter parameters are missing. @@ -142,8 +145,8 @@ class TestEnterpriseHelpers(unittest.TestCase): else: self.assertEqual(response, (args, kwargs)) - @mock.patch('util.enterprise_helpers.enterprise_enabled') - @mock.patch('util.enterprise_helpers.consent_necessary_for_course') + @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled') + @mock.patch('openedx.features.enterprise_support.api.consent_necessary_for_course') def test_data_consent_required_enterprise_disabled(self, mock_consent_necessary, mock_enterprise_enabled): @@ -158,8 +161,8 @@ class TestEnterpriseHelpers(unittest.TestCase): mock_enterprise_enabled.assert_called_once() mock_consent_necessary.assert_not_called() - @mock.patch('util.enterprise_helpers.enterprise_enabled') - @mock.patch('util.enterprise_helpers.consent_necessary_for_course') + @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled') + @mock.patch('openedx.features.enterprise_support.api.consent_necessary_for_course') def test_no_course_data_consent_required(self, mock_consent_necessary, mock_enterprise_enabled): @@ -176,9 +179,9 @@ class TestEnterpriseHelpers(unittest.TestCase): mock_enterprise_enabled.assert_called_once() mock_consent_necessary.assert_called_once() - @mock.patch('util.enterprise_helpers.enterprise_enabled') - @mock.patch('util.enterprise_helpers.consent_necessary_for_course') - @mock.patch('util.enterprise_helpers.get_enterprise_consent_url') + @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled') + @mock.patch('openedx.features.enterprise_support.api.consent_necessary_for_course') + @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): """ 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): mock_enterprise_enabled.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): """ Verify that get_enterprise_consent_url correctly builds URLs. @@ -264,7 +267,7 @@ class TestEnterpriseHelpers(unittest.TestCase): ) 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): mock_get_ece = ece_mock.objects.get ece_mock.DoesNotExist = Exception @@ -300,7 +303,7 @@ class TestEnterpriseHelpers(unittest.TestCase): expected_header = 'Enrollment in edX Demo Course was not complete.' 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): mock_get_ece = ece_mock.objects.get ece_mock.DoesNotExist = Exception