diff --git a/docs/decisions/0010-extract-enrollment-python-api.rst b/docs/decisions/0010-extract-enrollment-python-api.rst
index 1ad69caf4f7e..edf6a307f5f0 100644
--- a/docs/decisions/0010-extract-enrollment-python-api.rst
+++ b/docs/decisions/0010-extract-enrollment-python-api.rst
@@ -31,7 +31,8 @@ We are not changing the POST handler because it serves various use cases and par
performs authorization checks on request object, none of which are needed and would require careful
and rigorous testing of various enrollment flows, and also introduce risk of regressions if done in a single round of work.
-We will add a new function to the `enterprise_support` package in edx-platform to achieve this.
+We will add a new function to the `enterprise_support` package (since moved out of edx-platform
+and into edx-enterprise as `enterprise.platform_support`) to achieve this.
A few other features of the endpoint are also not needed in order to obtain the functionality needed
to replace the existing POST call:
diff --git a/lms/envs/common.py b/lms/envs/common.py
index ecfa2f2fc512..4af6df87d740 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -2061,7 +2061,6 @@
'openedx.features.calendar_sync',
'openedx.features.course_bookmarks',
'openedx.features.course_experience',
- 'openedx.features.enterprise_support.apps.EnterpriseSupportConfig',
'openedx.features.course_duration_limits',
'openedx.features.content_type_gating',
'openedx.features.discounts',
diff --git a/lms/urls.py b/lms/urls.py
index 512a21647f66..35842caa635e 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -3,6 +3,7 @@
"""
from config_models.views import ConfigurationModelCurrentAPIView
+from django.apps import apps as django_apps
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
@@ -51,7 +52,6 @@
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_authn.views.login import redirect_to_lms_login
-from openedx.features.enterprise_support.api import enterprise_enabled
RESET_COURSE_DEADLINES_NAME = 'reset_course_deadlines'
RENDER_XBLOCK_NAME = 'render_xblock'
@@ -880,8 +880,24 @@
path('api/third_party_auth/', include('common.djangoapps.third_party_auth.api.urls')),
]
+
+def _enterprise_enabled():
+ """
+ Whether the enterprise feature is installed and enabled.
+
+ Temporary duplicate of the helper that now lives in edx-enterprise
+ (``enterprise.platform_support.api.enterprise_enabled``), kept here only
+ because ``channel_integrations.urls`` still has to be included by the
+ platform. Remove this function and the whole block below once
+ enterprise-integrated-channels is modernized into a true plugin that
+ injects its own URLs via ``url_config``, at which point ``enterprise.urls``
+ moves there too.
+ """
+ return django_apps.is_installed('enterprise') and getattr(settings, 'ENABLE_ENTERPRISE_INTEGRATION', False)
+
+
# Enterprise
-if enterprise_enabled():
+if _enterprise_enabled():
urlpatterns += [
path('', include('enterprise.urls')),
path('', include('channel_integrations.urls')),
diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py
index c10dce3939d1..2ab611b2e54d 100644
--- a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py
+++ b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py
@@ -50,7 +50,6 @@
UserNotFound,
)
from openedx.core.djangolib.testing.utils import skip_unless_lms
-from openedx.features.enterprise_support.tests.factories import EnterpriseCustomerUserFactory
def mock_render_to_string(template_name, context):
@@ -264,16 +263,6 @@ def test_unsupported_social_link_platform(self):
with pytest.raises(AccountValidationError):
update_account_settings(self.user, {"social_links": social_links})
- def test_update_success_for_enterprise(self):
- EnterpriseCustomerUserFactory(user_id=self.user.id)
- level_of_education = "m"
- successful_update = {
- "level_of_education": level_of_education,
- }
- update_account_settings(self.user, successful_update)
- account_settings = get_account_settings(self.default_request)[0]
- assert level_of_education == account_settings['level_of_education']
-
@patch(
'openedx.core.djangoapps.user_api.accounts.api.AccountSettingsReadOnlyFieldsRequested.run_filter',
return_value=({'country'}, None),
diff --git a/openedx/features/enterprise_support/README.rst b/openedx/features/enterprise_support/README.rst
deleted file mode 100644
index 97e8fb9ffdb4..000000000000
--- a/openedx/features/enterprise_support/README.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-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/openedx/edx-enterprise`_.
diff --git a/openedx/features/enterprise_support/__init__.py b/openedx/features/enterprise_support/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/openedx/features/enterprise_support/admin/__init__.py b/openedx/features/enterprise_support/admin/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/openedx/features/enterprise_support/admin/forms.py b/openedx/features/enterprise_support/admin/forms.py
deleted file mode 100644
index e1788e0f2383..000000000000
--- a/openedx/features/enterprise_support/admin/forms.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""
-Enterprise support admin forms.
-"""
-
-from django import forms
-from django.utils.translation import gettext_lazy as _
-from enterprise.admin.utils import validate_csv
-
-
-class CSVImportForm(forms.Form): # pylint: disable=missing-class-docstring
- csv_file = forms.FileField(
- required=True,
- label=_('CSV File'),
- help_text=_('CSV file should have 3 columns having names lms_user_id, course_id, opportunity_id')
- )
-
- def clean_csv_file(self):
- csv_file = self.cleaned_data['csv_file']
- csv_reader = validate_csv(csv_file, expected_columns=['lms_user_id', 'course_id', 'opportunity_id'])
-
- return csv_reader
diff --git a/openedx/features/enterprise_support/admin/views.py b/openedx/features/enterprise_support/admin/views.py
deleted file mode 100644
index f377f7c668ce..000000000000
--- a/openedx/features/enterprise_support/admin/views.py
+++ /dev/null
@@ -1,71 +0,0 @@
-"""
-Enterprise support admin views.
-"""
-
-from django.contrib import messages
-from django.urls import reverse
-from django.utils.translation import gettext as _
-from django.views.generic.edit import FormView
-from enterprise.models import EnterpriseCourseEnrollment
-
-from common.djangoapps.student.models import CourseEnrollment, CourseEnrollmentAttribute
-from openedx.features.enterprise_support.admin.forms import CSVImportForm
-
-
-class EnrollmentAttributeOverrideView(FormView):
- """
- Learner Enrollment Attribute Override View.
- """
- template_name = 'enterprise_support/admin/enrollment_attributes_override.html'
- form_class = CSVImportForm
-
- @staticmethod
- def _get_admin_context(request):
- admin_context = {'opts': EnterpriseCourseEnrollment._meta}
- return admin_context
-
- def get_success_url(self):
- return reverse('admin:enterprise_override_attributes')
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context.update(self._get_admin_context(self.request))
- return context
-
- def form_valid(self, form):
- total_records = 0
- error_line_numbers = []
- csv_reader = form.cleaned_data['csv_file']
- for index, record in enumerate(csv_reader):
- total_records += 1
- try:
- course_enrollment = CourseEnrollment.objects.get(
- user_id=record['lms_user_id'],
- course_id=record['course_id'],
- )
- except CourseEnrollment.DoesNotExist:
- error_line_numbers.append(str(index + 1))
- else:
- CourseEnrollmentAttribute.objects.update_or_create(
- enrollment=course_enrollment,
- namespace='salesforce',
- name='opportunity_id',
- defaults={
- 'value': record['opportunity_id'],
- }
- )
-
- # if for some reason not a single enrollment updated than do not show success message.
- if len(error_line_numbers) != total_records:
- messages.success(self.request, 'Successfully updated learner enrollment opportunity ids.')
-
- if error_line_numbers:
- messages.error(
- self.request,
- _(
- 'Enrollment attributes were not updated for records at following line numbers '
- 'in csv because no enrollment found for these records: {error_line_numbers}'
- ).format(error_line_numbers=', '.join(error_line_numbers))
- )
-
- return super().form_valid(form)
diff --git a/openedx/features/enterprise_support/api.py b/openedx/features/enterprise_support/api.py
deleted file mode 100644
index 336426509363..000000000000
--- a/openedx/features/enterprise_support/api.py
+++ /dev/null
@@ -1,778 +0,0 @@
-"""
-APIs providing support for enterprise functionality.
-"""
-
-import logging
-import traceback
-from urllib.parse import urljoin
-
-import requests
-from crum import get_current_request
-from django.apps import apps as django_apps
-from django.conf import settings
-from django.contrib.auth.models import User # pylint: disable=imported-auth-user
-from django.core.cache import cache
-from django.template.loader import render_to_string
-from django.utils.translation import gettext as _
-from edx_django_utils.cache import get_cache_key
-from edx_rest_api_client.auth import SuppliedJwtAuth
-from requests.exceptions import HTTPError
-
-from common.djangoapps.third_party_auth.pipeline import get as get_partial_pipeline
-from common.djangoapps.third_party_auth.provider import Registry
-from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user
-from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
-from openedx.core.djangolib.markup import HTML, Text
-
-try:
- from consent.models import DataSharingConsent, DataSharingConsentTextOverrides
- from enterprise.api.v1.serializers import (
- EnterpriseCustomerUserReadOnlySerializer,
- EnterpriseCustomerUserWriteSerializer,
- )
- from enterprise.models import (
- EnterpriseCourseEnrollment,
- EnterpriseCustomer,
- EnterpriseCustomerUser,
- )
-except ImportError: # pragma: no cover
- pass
-
-
-CONSENT_FAILED_PARAMETER = 'consent_failed'
-LOGGER = logging.getLogger("edx.enterprise_helpers")
-ENTERPRISE_CUSTOMER_KEY_NAME = 'enterprise_customer'
-
-# See https://open-edx-proposals.readthedocs.io/en/latest/oep-0022-bp-django-caches.html#common-caching-defect-and-fix
-_CACHE_MISS = '__CACHE_MISS__'
-
-
-class EnterpriseApiException(Exception):
- """
- Exception for errors while communicating with the Enterprise service API.
- """
-
-
-class ConsentApiClient:
- """
- Class for producing an Enterprise Consent service API client
- """
-
- def __init__(self, user):
- """
- Initialize an authenticated Consent service API client by using the
- provided user.
- """
- jwt = create_jwt_for_user(user)
- base_api_url = configuration_helpers.get_value(
- 'ENTERPRISE_CONSENT_API_URL', settings.ENTERPRISE_CONSENT_API_URL
- )
- self.client = requests.Session()
- self.client.auth = SuppliedJwtAuth(jwt)
- self.consent_endpoint = urljoin(f"{base_api_url}/", "data_sharing_consent")
-
- def revoke_consent(self, **kwargs):
- """
- Revoke consent from any existing records that have it at the given scope.
-
- This endpoint takes any given kwargs, which are understood as filtering the
- conceptual scope of the consent involved in the request.
- """
- response = self.client.delete(self.consent_endpoint, json=kwargs)
- response.raise_for_status()
- return response.json()
-
- def provide_consent(self, **kwargs):
- """
- Provide consent at the given scope.
-
- This endpoint takes any given kwargs, which are understood as filtering the
- conceptual scope of the consent involved in the request.
- """
- response = self.client.post(self.consent_endpoint, json=kwargs)
- response.raise_for_status()
- return response.json()
-
- def consent_required(self, enrollment_exists=False, **kwargs):
- """
- Determine if consent is required at the given scope.
-
- This endpoint takes any given kwargs, which are understood as filtering the
- conceptual scope of the consent involved in the request.
- """
-
- # Call the endpoint with the given kwargs, and check the value that it provides.
- response = self.client.get(self.consent_endpoint, params=kwargs)
- response.raise_for_status()
- response = response.json()
-
- LOGGER.info(
- '[ENTERPRISE DSC] Consent Requirement Info. APIParams: [%s], APIResponse: [%s], EnrollmentExists: [%s]',
- kwargs,
- response,
- enrollment_exists,
- )
-
- # No Enterprise record exists, but we're already enrolled in a course. So, go ahead and proceed.
- if enrollment_exists and not response.get('exists', False):
- return False
-
- # In all other cases, just trust the Consent API.
- return response['consent_required']
-
-
-class EnterpriseServiceClientMixin:
- """
- Class for initializing an Enterprise API clients with service user.
- """
-
- def __init__(self):
- """
- Initialize an authenticated Enterprise API client by using the
- Enterprise worker user by default.
- """
- user = User.objects.get(username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME)
- super().__init__(user)
-
-
-class ConsentApiServiceClient(EnterpriseServiceClientMixin, ConsentApiClient):
- """
- Class for producing an Enterprise Consent API client with service user.
- """
-
-
-class EnterpriseApiClient:
- """
- Class for producing an Enterprise service API client.
- """
-
- def __init__(self, user):
- """
- Initialize an authenticated Enterprise service API client.
-
- Authentificate by jwt token using the provided user.
- """
- self.user = user
- jwt = create_jwt_for_user(user)
- self.base_api_url = configuration_helpers.get_value('ENTERPRISE_API_URL', settings.ENTERPRISE_API_URL)
- self.client = requests.Session()
- self.client.auth = SuppliedJwtAuth(jwt)
-
- def get_enterprise_customer(self, uuid):
- api_url = urljoin(f"{self.base_api_url}/", f"enterprise-customer/{uuid}/")
- response = self.client.get(api_url)
- response.raise_for_status()
- return response.json()
-
- def post_enterprise_course_enrollment(self, username, course_id):
- """
- Create an EnterpriseCourseEnrollment by using the corresponding serializer (for validation).
- """
- data = {
- 'username': username,
- 'course_id': course_id,
- }
- api_url = urljoin(f"{self.base_api_url}/", "enterprise-course-enrollment/")
- try:
- response = self.client.post(api_url, data=data)
- response.raise_for_status()
- except HTTPError:
- message = ( # noqa: UP032
- "An error occured while posting EnterpriseCourseEnrollment for user {username} and "
- "course run {course_id}."
- ).format(
- username=username,
- course_id=course_id,
- )
- LOGGER.exception(message)
- raise EnterpriseApiException(message) # pylint: disable=raise-missing-from # noqa: B904
-
- def fetch_enterprise_learner_data(self, user):
- """
- Fetch information related to enterprise from the Enterprise Service.
-
- Example:
- fetch_enterprise_learner_data(user)
-
- Argument:
- user: (User) django auth user
-
- Returns:
- dict:
- {
- "count": 1,
- "num_pages": 1,
- "current_page": 1,
- "next": null,
- "start": 0,
- "previous": null
- "results": [
- {
- "enterprise_customer": {
- "uuid": "cf246b88-d5f6-4908-a522-fc307e0b0c59",
- "name": "TestShib",
- "active": true,
- "site": {
- "domain": "example.com",
- "name": "example.com"
- },
- "enable_data_sharing_consent": true,
- "enforce_data_sharing_consent": "at_login",
- "branding_configuration": {
- "enterprise_customer": "cf246b88-d5f6-4908-a522-fc307e0b0c59",
- "logo": "https://open.edx.org/sites/all/themes/edx_open/logo.png"
- },
- "enterprise_customer_entitlements": [
- {
- "enterprise_customer": "cf246b88-d5f6-4908-a522-fc307e0b0c59",
- "entitlement_id": 69
- }
- ],
- "replace_sensitive_sso_username": False,
- },
- "user_id": 5,
- "user": {
- "username": "staff",
- "first_name": "",
- "last_name": "",
- "email": "staff@example.com",
- "is_staff": true,
- "is_active": true,
- "date_joined": "2016-09-01T19:18:26.026495Z"
- },
- "data_sharing_consent_records": [
- {
- "username": "staff",
- "enterprise_customer_uuid": "cf246b88-d5f6-4908-a522-fc307e0b0c59",
- "exists": true,
- "course_id": "course-v1:edX DemoX Demo_Course",
- "consent_provided": true,
- "consent_required": false
- }
- ]
- }
- ],
- }
- """
- if not user.is_authenticated:
- return None
-
- api_url = urljoin(f"{self.base_api_url}/", "enterprise-learner/")
-
- try:
- querystring = {'username': user.username}
- response = self.client.get(api_url, params=querystring)
- response.raise_for_status()
- except HTTPError:
- LOGGER.exception(
- 'Failed to get enterprise-learner for user [%s] with client user [%s]. Caller: %s, Request PATH: %s',
- user.username,
- self.user.username,
- "".join(traceback.format_stack()),
- get_current_request().META['PATH_INFO'],
- )
- return None
-
- return response.json()
-
-
-class EnterpriseApiServiceClient(EnterpriseServiceClientMixin, EnterpriseApiClient):
- """
- Class for producing an Enterprise service API client with service user.
- """
-
- def get_enterprise_customer(self, uuid):
- """
- Fetch enterprise customer with enterprise service user and cache the
- API response`.
- """
- enterprise_customer = enterprise_customer_from_cache(uuid=uuid)
- if enterprise_customer is _CACHE_MISS:
- api_url = urljoin(f"{self.base_api_url}/", f"enterprise-customer/{uuid}/")
- response = self.client.get(api_url)
- response.raise_for_status()
- enterprise_customer = response.json() if response.content else None
- if enterprise_customer:
- cache_enterprise(enterprise_customer)
-
- return enterprise_customer
-
-
-def activate_learner_enterprise(request, user, enterprise_customer):
- """
- Allow an enterprise learner to activate one of learner's linked enterprises.
- """
- serializer = EnterpriseCustomerUserWriteSerializer(data={
- 'enterprise_customer': enterprise_customer,
- 'username': user.username,
- 'active': True
- })
- if serializer.is_valid():
- serializer.save()
- enterprise_customer_user = EnterpriseCustomerUser.objects.get(
- user_id=user.id,
- enterprise_customer=enterprise_customer
- )
- enterprise_customer_user.update_session(request)
- LOGGER.info(
- '[Enterprise Selection Page] Learner activated an enterprise. User: %s, EnterpriseCustomer: %s',
- user.username,
- enterprise_customer,
- )
- return True
-
- return False
-
-
-def enterprise_enabled():
- """
- Determines whether the Enterprise app is installed
- """
- return django_apps.is_installed('enterprise') and getattr(settings, 'ENABLE_ENTERPRISE_INTEGRATION', False)
-
-
-def enterprise_is_enabled(otherwise=None):
- """Decorator which requires that the Enterprise feature be enabled before the function can run."""
- def decorator(func):
- """Decorator for ensuring the Enterprise feature is enabled."""
- def wrapper(*args, **kwargs):
- if enterprise_enabled():
- return func(*args, **kwargs)
- return otherwise
- return wrapper
- return decorator
-
-
-def get_enterprise_customer_cache_key(uuid, username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME):
- """The cache key used to get cached Enterprise Customer data."""
- return get_cache_key(
- resource='enterprise-customer',
- resource_id=uuid,
- username=username,
- )
-
-
-def cache_enterprise(enterprise_customer):
- """Add this customer's data to the Django cache."""
- cache_key = get_enterprise_customer_cache_key(enterprise_customer['uuid'])
- cache.set(cache_key, enterprise_customer, settings.ENTERPRISE_API_CACHE_TIMEOUT)
-
-
-def enterprise_customer_from_cache(uuid):
- """
- Retrieve enterprise customer data associated with the given ``uuid`` from the Django cache,
- returning a ``__CACHE_MISS__`` if absent.
- """
- cache_key = get_enterprise_customer_cache_key(uuid)
- return cache.get(cache_key, _CACHE_MISS)
-
-
-def add_enterprise_customer_to_session(request, enterprise_customer):
- """ Add the given enterprise_customer data to the request's session if user is authenticated. """
- if request.user.is_authenticated:
- request.session[ENTERPRISE_CUSTOMER_KEY_NAME] = enterprise_customer
-
-
-def enterprise_customer_from_session(request):
- """
- Retrieve enterprise_customer data from the request's session,
- returning a ``__CACHE_MISS__`` if absent.
-
- Now checks for session existence before attempting to access it.
- """
- if not request or not hasattr(request, 'session'):
- return _CACHE_MISS
- else:
- return request.session.get(ENTERPRISE_CUSTOMER_KEY_NAME, _CACHE_MISS)
-
-
-def enterprise_customer_uuid_from_session(request):
- """
- Retrieve an enterprise customer UUID from the request's session,
- returning a ``__CACHE_MISS__`` if absent. Note that this may
- return ``None``, which indicates that we've previously looked
- for an associated customer for this request's user, and
- none was present.
- """
- customer_data = enterprise_customer_from_session(request)
- if customer_data is not _CACHE_MISS:
- customer_data = customer_data or {}
- return customer_data.get('uuid')
- return _CACHE_MISS
-
-
-def enterprise_customer_uuid_from_query_param(request):
- """
- Returns an enterprise customer UUID from the given request's GET data,
- or ``__CACHE_MISS__`` if not present.
- """
- return request.GET.get(ENTERPRISE_CUSTOMER_KEY_NAME, _CACHE_MISS)
-
-
-def enterprise_customer_uuid_from_cookie(request):
- """
- Returns an enterprise customer UUID from the given request's cookies,
- or ``__CACHE_MISS__`` if not present.
- """
- return request.COOKIES.get(settings.ENTERPRISE_CUSTOMER_COOKIE_NAME, _CACHE_MISS)
-
-
-@enterprise_is_enabled()
-def enterprise_customer_from_api(request):
- """Use an API to get Enterprise Customer data from request context clues."""
- enterprise_customer = None
- enterprise_customer_uuid = enterprise_customer_uuid_for_request(request)
- if enterprise_customer_uuid is _CACHE_MISS:
- # enterprise_customer_uuid_for_request() `shouldn't` return a __CACHE_MISS__,
- # but just in case it does, we check for it and return early if found.
- return enterprise_customer
-
- if enterprise_customer_uuid:
- # If we were able to obtain an EnterpriseCustomer UUID, go ahead
- # and use it to attempt to retrieve EnterpriseCustomer details
- # from the EnterpriseCustomer API.
- enterprise_api_client = (
- EnterpriseApiClient(user=request.user)
- if request.user.is_authenticated
- else EnterpriseApiServiceClient()
- )
-
- try:
- enterprise_customer = enterprise_api_client.get_enterprise_customer(enterprise_customer_uuid)
- except HTTPError as err:
- if err.response.status_code == 404:
- enterprise_customer = None
- else:
- raise
- return enterprise_customer
-
-
-@enterprise_is_enabled()
-def enterprise_customer_uuid_for_request(request):
- """
- Check all the context clues of the request to gather a particular EnterpriseCustomer's UUID.
- """
- sso_provider_id = request.GET.get('tpa_hint')
- running_pipeline = get_partial_pipeline(request)
- if running_pipeline:
- # Determine if the user is in the middle of a third-party auth pipeline,
- # and set the sso_provider_id parameter to match if so.
- sso_provider_id = Registry.get_from_pipeline(running_pipeline).provider_id
-
- if sso_provider_id:
- # If we have a third-party auth provider, get the linked enterprise customer.
- try:
- # FIXME: Implement an Enterprise API endpoint where we can get the EC
- # directly via the linked SSO provider
- # Check if there's an Enterprise Customer such that the linked SSO provider
- # has an ID equal to the ID we got from the running pipeline or from the
- # request tpa_hint URL parameter.
- enterprise_customer_uuid = EnterpriseCustomer.objects.get(
- enterprise_customer_identity_providers__provider_id=sso_provider_id
- ).uuid
- except EnterpriseCustomer.DoesNotExist:
- LOGGER.info(
- '[ENTERPRISE DSC] Customer not found using SSO Provider ID. User: [%s], SSOProviderID: [%s]',
- request.user.username,
- sso_provider_id
- )
- enterprise_customer_uuid = None
- else:
- enterprise_customer_uuid = _customer_uuid_from_query_param_cookies_or_session(request)
-
- if enterprise_customer_uuid is _CACHE_MISS or enterprise_customer_uuid is None:
- if not request.user.is_authenticated:
- return None
-
- # If there's no way to get an Enterprise UUID for the request, check to see
- # if there's already an Enterprise attached to the requesting user on the backend.
- enterprise_customer = None
- learner_data = get_enterprise_learner_data_from_db(request.user)
- if learner_data:
- enterprise_customer = learner_data[0]['enterprise_customer']
- enterprise_customer_uuid = enterprise_customer['uuid']
- cache_enterprise(enterprise_customer)
- else:
- enterprise_customer_uuid = None
-
- # Now that we've asked the database for this users's enterprise customer data,
- # add it to their session (even if it's null/empty, which indicates the user
- # has no associated enterprise customer).
- LOGGER.info(
- '[ENTERPRISE DSC] Updating Session. User: [%s], UserAuthenticated: [%s], EnterpriseCustomer: [%s]',
- request.user.username,
- request.user.is_authenticated,
- enterprise_customer
- )
- add_enterprise_customer_to_session(request, enterprise_customer)
-
- return enterprise_customer_uuid
-
-
-def _customer_uuid_from_query_param_cookies_or_session(request):
- """
- Helper function that plucks a customer UUID out of the given requests's
- query params, cookie, or session data.
- Returns ``__CACHE_MISS__`` if none of those keys are present in the request.
- """
- for function in (
- enterprise_customer_uuid_from_query_param,
- enterprise_customer_uuid_from_cookie,
- enterprise_customer_uuid_from_session,
- ):
- enterprise_customer_uuid = function(request)
- if enterprise_customer_uuid is not _CACHE_MISS:
- LOGGER.info(
- '[ENTERPRISE DSC] Customer Info. User: [%s], Function: [%s], UUID: [%s]',
- request.user.username,
- function,
- enterprise_customer_uuid
- )
- return enterprise_customer_uuid
-
- return _CACHE_MISS
-
-
-@enterprise_is_enabled()
-def enterprise_customer_for_request(request):
- """
- Check all the context clues of the request to determine if
- the request being made is tied to a particular EnterpriseCustomer.
- """
- enterprise_customer = enterprise_customer_from_session(request)
- if enterprise_customer is _CACHE_MISS:
- enterprise_customer = enterprise_customer_from_api(request)
- LOGGER.info(
- '[ENTERPRISE DSC] Updating Session. User: [%s], UserAuthenticated: [%s], EnterpriseCustomer: [%s]',
- request.user.username,
- request.user.is_authenticated,
- enterprise_customer
- )
- add_enterprise_customer_to_session(request, enterprise_customer)
- return enterprise_customer
-
-
-@enterprise_is_enabled()
-def get_enterprise_learner_data_from_api(user):
- """
- Client API operation adapter/wrapper
- """
- if user.is_authenticated:
- enterprise_learner_data = EnterpriseApiClient(user=user).fetch_enterprise_learner_data(user)
- if enterprise_learner_data:
- return enterprise_learner_data['results']
-
-
-@enterprise_is_enabled()
-def get_enterprise_learner_data_from_db(user):
- """
- Query the database directly and use the same serializer that the api call would use to return the same results.
- """
- if user.is_authenticated:
- queryset = EnterpriseCustomerUser.objects.filter(user_id=user.id)
- serializer = EnterpriseCustomerUserReadOnlySerializer(queryset, many=True)
- return serializer.data
-
-
-@enterprise_is_enabled(otherwise=[])
-def get_data_sharing_consents(user):
- """
- Returns a list of data sharing consent records for the given user.
- """
-
- return DataSharingConsent.objects.filter(
- username=user.username
- )
-
-
-@enterprise_is_enabled(otherwise=[])
-def get_enterprise_course_enrollments(user):
- """
- Returns a list of enterprise course enrollments for the given user.
- """
-
- return EnterpriseCourseEnrollment.objects.select_related(
- 'licensedenterprisecourseenrollment_enrollment_fulfillment',
- 'enterprise_customer_user'
- ).prefetch_related(
- 'enterprise_customer_user__enterprise_customer'
- ).filter(
- enterprise_customer_user__user_id=user.id
- )
-
-
-@enterprise_is_enabled()
-def enterprise_customer_from_session_or_learner_data(request):
- """
- Returns an Enterprise Customer for the authenticated user.
-
- Retrieves customer from session by default. If _CACHE_MISS, retrieve customer using
- learner data from the DB and add customer data to the session.
-
- Args:
- request: request made to the LMS dashboard
- """
- enterprise_customer = enterprise_customer_from_session(request)
- if enterprise_customer is _CACHE_MISS:
- learner_data = get_enterprise_learner_data_from_db(request.user)
- enterprise_customer = learner_data[0]['enterprise_customer'] if learner_data else None
- # Add to session cache regardless of whether it is null
- LOGGER.info(
- '[ENTERPRISE DSC] Updating Session. User: [%s], UserAuthenticated: [%s], EnterpriseCustomer: [%s]',
- request.user.username,
- request.user.is_authenticated,
- enterprise_customer
- )
- add_enterprise_customer_to_session(request, enterprise_customer)
- if enterprise_customer:
- cache_enterprise(enterprise_customer)
- return enterprise_customer
-
-
-@enterprise_is_enabled()
-def get_enterprise_learner_portal_enabled_message(enterprise_customer):
- """
- Returns message to be displayed in dashboard if the user is linked to an Enterprise with the Learner Portal enabled.
- Note: request.session[ENTERPRISE_CUSTOMER_KEY_NAME] will be used in case the user is linked to
- multiple Enterprises. Otherwise, it won't exist and the Enterprise Learner data
- will be used. If that doesn't exist return None.
- Args:
- enterprise_customer: EnterpriseCustomer object
- """
- if not enterprise_customer:
- return None
-
- if not enterprise_customer.get('enable_learner_portal', False):
- return None
-
- learner_portal_url = "{base_url}/{slug}?utm_source=lms_dashboard_banner".format(
- base_url=settings.ENTERPRISE_LEARNER_PORTAL_BASE_URL,
- slug=enterprise_customer['slug']
- )
-
- return Text(_(
- "You have access to the {bold_start}{enterprise_name}{bold_end} dashboard. "
- "To access the courses available to you through {enterprise_name}, "
- "{link_start}visit the {enterprise_name} dashboard{link_end}."
- )).format(
- enterprise_name=enterprise_customer['name'],
- bold_start=HTML(""),
- bold_end=HTML(""),
- link_start=HTML(f""),
- link_end=HTML(""),
- )
-
-
-@enterprise_is_enabled(otherwise={})
-def get_enterprise_learner_portal_context(request):
- """
- Determines a selected enterprise customer from session or learner data from the DB.
-
- Arguments:
- request: A request object.
-
- Returns:
- dict: A dictionary representing the necessary metadata and messaging about an Enterprise Learner Portal,
- used in the dashboard.html template.
- """
- context = {}
- enterprise_customer = enterprise_customer_from_session_or_learner_data(request)
- if not enterprise_customer:
- return context
-
- enterprise_learner_portal_enabled_message = get_enterprise_learner_portal_enabled_message(enterprise_customer)
- context.update({
- 'enterprise_customer_name': enterprise_customer.get('name'),
- 'enterprise_customer_slug': enterprise_customer.get('slug'),
- 'enterprise_customer_learner_portal_enabled': enterprise_customer.get('enable_learner_portal', False),
- 'enterprise_customer_uuid': enterprise_customer.get('uuid'),
- 'enterprise_learner_portal_base_url': settings.ENTERPRISE_LEARNER_PORTAL_BASE_URL,
- 'enterprise_learner_portal_enabled_message': enterprise_learner_portal_enabled_message,
- })
- return context
-
-
-@enterprise_is_enabled()
-def get_consent_notification_data(enterprise_customer):
- """
- Returns the consent notification data from DataSharingConsentPage modal
- """
- title_template = None
- message_template = None
- try:
- consent_page = DataSharingConsentTextOverrides.objects.get(enterprise_customer_id=enterprise_customer['uuid'])
- title_template = consent_page.declined_notification_title
- message_template = consent_page.declined_notification_message
- except DataSharingConsentTextOverrides.DoesNotExist:
- LOGGER.info(
- "DataSharingConsentPage object doesn't exit for {enterprise_customer_name}".format(
- enterprise_customer_name=enterprise_customer['name']
- )
- )
- return title_template, message_template
-
-
-@enterprise_is_enabled(otherwise='')
-def get_dashboard_consent_notification(request, user, course_enrollments):
- """
- If relevant to the request at hand, create a banner on the dashboard indicating consent failed.
-
- Args:
- request: The WSGIRequest object produced by the user browsing to the Dashboard page.
- user: The logged-in user
- course_enrollments: A list of the courses to be rendered on the Dashboard page.
-
- Returns:
- str: Either an empty string, or a string containing the HTML code for the notification banner.
- """
- enrollment = None
- consent_needed = False
- course_id = request.GET.get(CONSENT_FAILED_PARAMETER)
-
- if course_id:
-
- enterprise_customer = enterprise_customer_for_request(request)
- if not enterprise_customer:
- return ''
-
- for course_enrollment in course_enrollments:
- if str(course_enrollment.course_id) == course_id:
- enrollment = course_enrollment
- break
-
- client = ConsentApiClient(user=request.user)
- consent_needed = client.consent_required(
- enterprise_customer_uuid=enterprise_customer['uuid'],
- username=user.username,
- course_id=course_id,
- )
-
- if consent_needed and enrollment:
-
- title_template, message_template = get_consent_notification_data(enterprise_customer)
- if not title_template:
- title_template = _(
- 'Enrollment in {course_title} was not complete.'
- )
- if not message_template:
- message_template = _(
- 'If you have concerns about sharing your data, please contact your administrator '
- 'at {enterprise_customer_name}.'
- )
-
- title = title_template.format(
- course_title=enrollment.course_overview.display_name,
- )
- message = message_template.format(
- enterprise_customer_name=enterprise_customer['name'],
- )
-
- return render_to_string(
- 'enterprise_support/enterprise_consent_declined_notification.html',
- {
- 'title': title,
- 'message': message,
- 'course_name': enrollment.course_overview.display_name,
- }
- )
- return ''
diff --git a/openedx/features/enterprise_support/apps.py b/openedx/features/enterprise_support/apps.py
deleted file mode 100644
index e664beff53c5..000000000000
--- a/openedx/features/enterprise_support/apps.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
-Configuration for enterprise_support
-"""
-
-
-from django.apps import AppConfig
-
-
-class EnterpriseSupportConfig(AppConfig):
- """
- Configuration class for enterprise_support
- """
- name = 'openedx.features.enterprise_support'
-
- def ready(self):
- # Import signals to activate signal handler for enterprise.
- from . import signals # pylint: disable=unused-import # noqa: F401
diff --git a/openedx/features/enterprise_support/context.py b/openedx/features/enterprise_support/context.py
deleted file mode 100644
index 540cfb799853..000000000000
--- a/openedx/features/enterprise_support/context.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""
-APIs providing enterprise context for events.
-"""
-from enterprise.models import EnterpriseCourseEnrollment
-
-
-def get_enterprise_event_context(user_id, course_id):
- """
- Creates an enterprise context from a `course_id` anf `user_id`.
- Example Returned Context::
- {
- 'enterprise_uuid': '1a0fbcbe-49e5-42f1-8e83-4cddfa592f22'
- }
- Arguments:
- user_id: id of user object.
- course_id: id of course object.
- Returns:
- dict: A dictionary representing the enterprise uuid.
- """
- # Prevent a circular import.
- from openedx.features.enterprise_support.utils import is_enterprise_learner
- context = {}
- if is_enterprise_learner(user_id):
- uuids = EnterpriseCourseEnrollment.get_enterprise_uuids_with_user_and_course(str(user_id), str(course_id))
- if uuids:
- context.update({"enterprise_uuid": str(uuids[0])})
- return context
diff --git a/openedx/features/enterprise_support/enrollments/__init__.py b/openedx/features/enterprise_support/enrollments/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/openedx/features/enterprise_support/enrollments/exceptions.py b/openedx/features/enterprise_support/enrollments/exceptions.py
deleted file mode 100644
index 58d9a1484f50..000000000000
--- a/openedx/features/enterprise_support/enrollments/exceptions.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# pylint: disable=missing-module-docstring
-
-class CourseIdMissingException(Exception):
- """
- course_id missing
- """
-
-
-class UserDoesNotExistException(Exception):
- """
- course_id invalid
- """
diff --git a/openedx/features/enterprise_support/enrollments/tests/__init__.py b/openedx/features/enterprise_support/enrollments/tests/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/openedx/features/enterprise_support/enrollments/tests/test_utils.py b/openedx/features/enterprise_support/enrollments/tests/test_utils.py
deleted file mode 100644
index 632bd42a1cbd..000000000000
--- a/openedx/features/enterprise_support/enrollments/tests/test_utils.py
+++ /dev/null
@@ -1,279 +0,0 @@
-"""
-Test the enterprise support utils.
-"""
-from unittest import mock
-from unittest.case import TestCase
-
-from django.core.exceptions import ObjectDoesNotExist
-from opaque_keys.edx.keys import CourseKey
-
-from openedx.core.djangoapps.course_groups.cohorts import CourseUserGroup
-from openedx.core.djangoapps.enrollments.errors import CourseEnrollmentError, CourseEnrollmentExistsError
-from openedx.core.djangolib.testing.utils import skip_unless_lms
-from openedx.features.enterprise_support.enrollments.exceptions import (
- CourseIdMissingException,
- UserDoesNotExistException,
-)
-from openedx.features.enterprise_support.enrollments.utils import lms_update_or_create_enrollment
-
-COURSE_STRING = 'course-v1:OpenEdX+OutlineCourse+Run3'
-ENTERPRISE_UUID = 'enterprise_uuid'
-COURSE_ID = CourseKey.from_string(COURSE_STRING)
-USERNAME = 'test'
-USER_ID = 1223
-COURSE_MODE = 'verified'
-
-
-@skip_unless_lms
-class EnrollmentUtilsTest(TestCase):
- """
- Test enterprise support utils.
- """
-
- def setUp(self):
- super().setUp()
- self.a_user = mock.MagicMock()
- self.a_user.id = USER_ID
- self.a_user.username = USERNAME
-
- def test_validation_of_inputs_course_id(self):
- with self.assertRaises(CourseIdMissingException): # noqa: PT027
- lms_update_or_create_enrollment(
- USERNAME, None, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
- )
-
- def test_validation_of_inputs_user_not_provided(self):
- with self.assertRaises(UserDoesNotExistException): # noqa: PT027
- lms_update_or_create_enrollment(
- None, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
- )
-
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
- def test_validation_of_inputs_user_not_found(
- self,
- mock_tx,
- mock_user_model,
- ):
- mock_tx.return_value.atomic.side_effect = None
- mock_user_model.side_effect = ObjectDoesNotExist()
- with self.assertRaises(UserDoesNotExistException): # noqa: PT027
- lms_update_or_create_enrollment(
- USERNAME, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
- )
-
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
- def test_course_enrollment_error_raises(
- self,
- mock_tx,
- mock_user_model,
- mock_get_enrollment_api,
- mock_add_enrollment_api,
- ):
- mock_add_enrollment_api.side_effect = CourseEnrollmentError("test")
- mock_tx.return_value.atomic.side_effect = None
-
- mock_user_model.return_value = self.a_user
- mock_get_enrollment_api.return_value = None
- with self.assertRaises(CourseEnrollmentError): # noqa: PT027
- lms_update_or_create_enrollment(
- USERNAME, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
- )
- mock_get_enrollment_api.assert_called_once_with(USERNAME, str(COURSE_ID))
-
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
- def test_course_group_error_raises(
- self,
- mock_tx,
- mock_user_model,
- mock_get_enrollment_api,
- mock_add_enrollment_api,
- ):
- mock_add_enrollment_api.side_effect = CourseUserGroup.DoesNotExist()
- mock_tx.return_value.atomic.side_effect = None
-
- mock_user_model.return_value = self.a_user
- mock_get_enrollment_api.return_value = None
- with self.assertRaises(CourseUserGroup.DoesNotExist): # noqa: PT027
- lms_update_or_create_enrollment(
- USERNAME, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
- )
- mock_get_enrollment_api.assert_called_once_with(USERNAME, str(COURSE_ID))
-
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
- def test_calls_enrollment_and_cohort_apis(
- self,
- mock_tx,
- mock_user_model,
- mock_get_enrollment_api,
- mock_add_enrollment_api,
- ):
- expected_response = {'mode': COURSE_MODE, 'is_active': True}
-
- mock_add_enrollment_api.return_value = expected_response
- mock_tx.return_value.atomic.side_effect = None
-
- mock_user_model.return_value = self.a_user
- mock_get_enrollment_api.return_value = None
-
- response = lms_update_or_create_enrollment(
- USERNAME, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
- )
- assert response == expected_response
- mock_add_enrollment_api.assert_called_once_with(
- USERNAME,
- str(COURSE_ID),
- mode=COURSE_MODE,
- is_active=True,
- enrollment_attributes=None,
- enterprise_uuid=ENTERPRISE_UUID,
- force_enrollment=False,
- )
- mock_get_enrollment_api.assert_called_once_with(USERNAME, str(COURSE_ID))
-
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
- def test_passes_force_enrollment_flag(
- self,
- mock_tx,
- mock_user_model,
- mock_get_enrollment_api,
- mock_add_enrollment_api,
- ):
- """
- Everything about this test is the same as the standard happy case, except we're just making sure the
- force_enrollment flag gets passed to add_enrollment().
- """
- expected_response = {'mode': COURSE_MODE, 'is_active': True}
-
- mock_add_enrollment_api.return_value = expected_response
- mock_tx.return_value.atomic.side_effect = None
-
- mock_user_model.return_value = self.a_user
- mock_get_enrollment_api.return_value = None
-
- response = lms_update_or_create_enrollment(
- USERNAME, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID, force_enrollment=True
- )
- assert response == expected_response
- mock_add_enrollment_api.assert_called_once_with(
- USERNAME,
- str(COURSE_ID),
- mode=COURSE_MODE,
- is_active=True,
- enrollment_attributes=None,
- enterprise_uuid=ENTERPRISE_UUID,
- force_enrollment=True, # Literally the only purpose of this test.
- )
- mock_get_enrollment_api.assert_called_once_with(USERNAME, str(COURSE_ID))
-
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
- def test_existing_enrollment_does_not_fail(
- self,
- mock_tx,
- mock_user_model,
- mock_get_enrollment_api,
- mock_add_enrollment_api,
- ):
- expected_response = {'mode': COURSE_MODE, 'is_active': True}
- enrollment_response = {'mode': COURSE_MODE, 'is_active': True}
-
- mock_add_enrollment_api.side_effect = CourseEnrollmentExistsError("test", {})
- mock_tx.return_value.atomic.side_effect = None
-
- mock_get_enrollment_api.return_value = enrollment_response
- mock_user_model.return_value = self.a_user
-
- response = lms_update_or_create_enrollment(
- USERNAME, COURSE_ID, COURSE_MODE, is_active=True, enterprise_uuid=ENTERPRISE_UUID
- )
- mock_add_enrollment_api.assert_not_called()
- assert response == expected_response
- mock_get_enrollment_api.assert_called_once()
-
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.update_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
- def test_upgrade_user_enrollment_mode(
- self,
- mock_tx,
- mock_user_model,
- mock_add_enrollment_api,
- mock_get_enrollment_api,
- mock_update_enrollment_api,
- ):
- enrollment_response = {'mode': COURSE_MODE, 'is_active': True}
- mock_get_enrollment_api.return_value = {
- 'mode': 'audit',
- 'is_active': True,
- }
-
- mock_update_enrollment_api.return_value = {
- 'mode': 'verified',
- 'is_active': True,
- }
- mock_tx.return_value.atomic.side_effect = None
- mock_user_model.return_value = self.a_user
-
- upgraded_enrollment = lms_update_or_create_enrollment(
- USERNAME, COURSE_ID, desired_mode=COURSE_MODE, is_active=True
- )
-
- assert upgraded_enrollment == enrollment_response
- mock_update_enrollment_api.assert_called_once_with(
- USERNAME,
- str(COURSE_ID),
- mode='verified',
- is_active=True,
- enrollment_attributes=None,
- )
-
- mock_get_enrollment_api.assert_called_once_with(USERNAME, str(COURSE_ID))
- mock_add_enrollment_api.assert_not_called()
-
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.update_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.get_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.enrollment_api.add_enrollment')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.User.objects.get')
- @mock.patch('openedx.features.enterprise_support.enrollments.utils.transaction')
- def test_upgrade_user_enrollment_mode_already_verified(
- self,
- mock_tx,
- mock_user_model,
- mock_add_enrollment_api,
- mock_get_enrollment_api,
- mock_update_enrollment_api,
- ):
- existing_enrollment = {
- 'mode': 'verified',
- 'is_active': True,
- }
- mock_get_enrollment_api.return_value = existing_enrollment
-
- mock_tx.return_value.atomic.side_effect = None
- mock_user_model.return_value = self.a_user
-
- upgraded_enrollment = lms_update_or_create_enrollment(
- USERNAME, COURSE_ID, desired_mode='verified', is_active=True
- )
-
- assert upgraded_enrollment == existing_enrollment
- mock_update_enrollment_api.assert_not_called()
- mock_get_enrollment_api.assert_called_once()
- mock_add_enrollment_api.assert_not_called()
diff --git a/openedx/features/enterprise_support/enrollments/utils.py b/openedx/features/enterprise_support/enrollments/utils.py
deleted file mode 100644
index 92193358967a..000000000000
--- a/openedx/features/enterprise_support/enrollments/utils.py
+++ /dev/null
@@ -1,175 +0,0 @@
-"""
-Utils for use in enrollment codebase such as views.
-"""
-import logging
-
-from django.core.exceptions import ObjectDoesNotExist # pylint: disable=wrong-import-order
-from django.db import transaction
-
-from common.djangoapps.student.models import User
-from openedx.core.djangoapps.enrollments import api as enrollment_api
-from openedx.core.djangoapps.enrollments.errors import (
- CourseEnrollmentError,
- CourseEnrollmentExistsError,
- CourseEnrollmentNotUpdatableError,
-)
-from openedx.core.lib.log_utils import audit_log
-from openedx.features.enterprise_support.enrollments.exceptions import (
- CourseIdMissingException,
- UserDoesNotExistException,
-)
-
-log = logging.getLogger(__name__)
-
-
-def lms_update_or_create_enrollment(
- username,
- course_id,
- desired_mode,
- is_active,
- enterprise_uuid=None,
- force_enrollment=False,
-):
- """
- Update or create the user's course enrollment based on the existing enrollment mode.
- If an enrollment exists and its mode is not equal to the desired mode,
- then it updates the enrollment.
- Otherwise, it creates a new enrollment.
- Enrollment function meant to be called by edx-enterprise to replace the
- current uses of the EnrollmentApiClient
- The REST enrollment endpoint may also eventually also want to reuse this function
- since it's a subset of what the endpoint handles
-
- Unlike the REST endpoint, this function does not check for enterprise enabled, or user api key
- permissions etc. Those concerns are still going to be used by REST endpoint but this function
- is meant for use from within edx-enterprise hence already presume such privileges.
-
- Arguments:
- - username (str): User name
- - course_id (obj) : Course key obtained using CourseKey.from_string(course_id_input)
- - desired_mode (CourseMode): desired course mode
- - is_active (bool): A Boolean value that indicates whether the
- enrollment is to be set to inactive (if False). Usually we want a True if enrolling anew.
- - enterprise_uuid (str): Optional. id to identify the enterprise to enroll under
- - force_enrollment (bool):
- Enroll user even if course enrollment_end date is expired (default False). This only has an effect when the
- enrollment is being created, not when it is only updated.
-
- Returns: A serializable dictionary of the new or updated course enrollment. If it hits
- CourseEnrollmentError or CourseEnrollmentNotUpdatableError, it raises those exceptions.
- In case of the add_enrollment call, it returns None if the enrollment already exists and
- the desired_mode or is_active match the existing enrollment.
- """
- user = _validate_enrollment_inputs(username, course_id)
- current_enrollment = enrollment_api.get_enrollment(username, str(course_id))
- response = None
- if (
- current_enrollment
- and current_enrollment['mode'] == desired_mode
- and current_enrollment['is_active'] == is_active
- ):
- log.info(
- "Existing enrollment [%s] for user [%s] matches desired enrollment. No action taken.",
- current_enrollment,
- username,
- )
- return current_enrollment
- with transaction.atomic():
- try:
- if current_enrollment:
- response = enrollment_api.update_enrollment(
- username,
- str(course_id),
- mode=desired_mode,
- is_active=is_active,
- enrollment_attributes=None,
- )
- if not response or (
- response['mode'] != desired_mode or
- response['is_active'] != is_active
- ):
- log.exception(
- "An error occurred while updating the course enrollment for user "
- "[%s]: course run = [%s], enterprise_uuid = [%s], is_active = [%s], ",
- username,
- course_id,
- str(enterprise_uuid),
- is_active,
- )
- raise CourseEnrollmentNotUpdatableError(
- f"Unable to upgrade enrollment for user {username} "
- "in course {course_id} to {desired_mode} mode."
- "Response from update_enrollment: {response}"
- )
- else:
- response = enrollment_api.add_enrollment(
- username,
- str(course_id),
- mode=desired_mode,
- is_active=is_active,
- enrollment_attributes=None,
- enterprise_uuid=enterprise_uuid,
- force_enrollment=force_enrollment,
- )
- if not response:
- log.exception(
- "An error occurred while creating the new course enrollment for user "
- "[%s] in course run [%s]",
- username,
- course_id,
- )
- raise CourseEnrollmentError(
- f"Unable to create enrollment for user {username} in course {course_id}."
- )
- except CourseEnrollmentExistsError as error:
- # This will rarely be raised when we hit a race condition in adding a net-new enrollment
- log.warning(
- "An enrollment [%s] already exists for user [%s] in course run [%s].",
- error.enrollment,
- username,
- course_id,
- )
- return None
- except (CourseEnrollmentError, CourseEnrollmentNotUpdatableError) as error:
- log.exception(
- "Raising error [%s] for user "
- "[%s]: course run = [%s], enterprise_uuid = [%s], is_active = [%s], force_enrollment = [%s], ",
- error,
- username,
- course_id,
- str(enterprise_uuid),
- is_active,
- force_enrollment,
- )
- raise error
- finally:
- final_enrollment = response or current_enrollment
- audit_log(
- 'enrollment_change_requested',
- course_id=str(course_id),
- requested_mode=desired_mode,
- actual_mode=final_enrollment['mode'] if final_enrollment else None,
- requested_activation=is_active,
- actual_activation=final_enrollment['is_active'] if final_enrollment else None,
- user_id=user.id
- )
- return response
-
-
-def _validate_enrollment_inputs(username, course_id):
- """
- Validates username and course_id.
- Raises:
- - UserDoesNotExistException if user not found.
- - CourseIdMissingException if course_id not provided.
- """
- if not course_id:
- raise CourseIdMissingException("Course ID must be specified to create a new enrollment.")
- if not username:
- raise UserDoesNotExistException('username is a required argument for enrollment')
- try:
- # Lookup the user, instead of using request.user, since request.user may not match the username POSTed.
- user = User.objects.get(username=username)
- except ObjectDoesNotExist as error:
- raise UserDoesNotExistException(f'The user {username} does not exist.') from error
- return user
diff --git a/openedx/features/enterprise_support/serializers.py b/openedx/features/enterprise_support/serializers.py
deleted file mode 100644
index 030d4160c098..000000000000
--- a/openedx/features/enterprise_support/serializers.py
+++ /dev/null
@@ -1,45 +0,0 @@
-"""
-Defines serializers for enterprise_support.
-"""
-
-
-from rest_framework import serializers
-
-try:
- from enterprise.api.v1.serializers import (
- EnterpriseCourseEnrollmentReadOnlySerializer as BaseEnterpriseCourseEnrollmentSerializer,
- )
- from enterprise.models import EnterpriseCourseEnrollment
-except ImportError: # pragma: no cover
- pass
-
-
-class EnterpriseCourseEnrollmentSerializer(BaseEnterpriseCourseEnrollmentSerializer):
- """
- Serializer for EnterpriseCourseEnrollment model.
- """
-
- enterprise_customer_name = serializers.SerializerMethodField()
- license = serializers.SerializerMethodField()
-
- class Meta:
- model = EnterpriseCourseEnrollment
- fields = (
- 'course_id',
- 'enterprise_customer_name',
- 'enterprise_customer_user_id',
- 'license',
- 'saved_for_later'
- )
-
- def get_enterprise_customer_name(self, obj):
- return obj.enterprise_customer_user.enterprise_customer.name
-
- def get_license(self, obj):
- licensed_ece = obj.license
-
- if licensed_ece:
- return {
- 'uuid': str(licensed_ece.license_uuid),
- 'is_revoked': licensed_ece.is_revoked
- }
diff --git a/openedx/features/enterprise_support/signals.py b/openedx/features/enterprise_support/signals.py
deleted file mode 100644
index 7fbb366ba7e0..000000000000
--- a/openedx/features/enterprise_support/signals.py
+++ /dev/null
@@ -1,124 +0,0 @@
-"""
-This module contains signals related to enterprise.
-"""
-
-
-import logging
-
-from django.conf import settings
-from django.contrib.auth.models import User # pylint: disable=imported-auth-user
-from django.db.models.signals import post_save, pre_save
-from django.dispatch import receiver
-from enterprise.models import EnterpriseCourseEnrollment, EnterpriseCustomer
-from integrated_channels.integrated_channel.tasks import (
- transmit_single_learner_data,
- transmit_single_subsection_learner_data,
-)
-from slumber.exceptions import HttpClientError
-
-from common.djangoapps.student.signals import UNENROLL_DONE
-from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
-from openedx.core.djangoapps.signals.signals import COURSE_ASSESSMENT_GRADE_CHANGED, COURSE_GRADE_NOW_PASSED
-from openedx.features.enterprise_support.tasks import clear_enterprise_customer_data_consent_share_cache
-from openedx.features.enterprise_support.utils import clear_data_consent_share_cache, is_enterprise_learner
-
-log = logging.getLogger(__name__)
-
-
-@receiver(post_save, sender=EnterpriseCourseEnrollment)
-def update_dsc_cache_on_course_enrollment(sender, instance, **kwargs): # pylint: disable=unused-argument
- """
- clears data_sharing_consent_needed cache after Enterprise Course Enrollment
- """
- clear_data_consent_share_cache(
- instance.enterprise_customer_user.user_id,
- instance.course_id,
- str(instance.enterprise_customer_user.enterprise_customer.uuid)
- )
-
-
-@receiver(pre_save, sender=EnterpriseCustomer)
-def update_dsc_cache_on_enterprise_customer_update(sender, instance, **kwargs):
- """
- clears data_sharing_consent_needed cache after enable_data_sharing_consent flag is changed.
- """
- old_instance = sender.objects.filter(pk=instance.uuid).first()
- if old_instance: # instance already exists, so it's updating.
- new_value = instance.enable_data_sharing_consent
- old_value = old_instance.enable_data_sharing_consent
- if new_value != old_value:
- kwargs = {'enterprise_customer_uuid': str(instance.uuid)}
- result = clear_enterprise_customer_data_consent_share_cache.apply_async(kwargs=kwargs)
- log.info("DSC: Created {task_name}[{task_id}] with arguments {kwargs}".format( # noqa: UP032
- task_name=clear_enterprise_customer_data_consent_share_cache.name,
- task_id=result.task_id,
- kwargs=kwargs,
- ))
-
-
-@receiver(COURSE_GRADE_NOW_PASSED, dispatch_uid="new_passing_enterprise_learner")
-def handle_enterprise_learner_passing_grade(sender, user, course_id, **kwargs): # pylint: disable=unused-argument
- """
- Listen for a learner passing a course, transmit data to relevant integrated channel
- """
- if is_enterprise_learner(user):
- kwargs = {
- 'username': str(user.username),
- 'course_run_id': str(course_id)
- }
-
- transmit_single_learner_data.apply_async(kwargs=kwargs)
-
-
-@receiver(COURSE_ASSESSMENT_GRADE_CHANGED)
-def handle_enterprise_learner_subsection(sender, user, course_id, subsection_id, subsection_grade, **kwargs): # pylint: disable=unused-argument
- """
- Listen for an enterprise learner completing a subsection, transmit data to relevant integrated channel.
- """
- if is_enterprise_learner(user):
- kwargs = {
- 'username': str(user.username),
- 'course_run_id': str(course_id),
- 'subsection_id': str(subsection_id),
- 'grade': str(subsection_grade),
- }
-
- transmit_single_subsection_learner_data.apply_async(kwargs=kwargs)
-
-
-@receiver(UNENROLL_DONE)
-def refund_order_voucher(sender, course_enrollment, skip_refund=False, **kwargs): # pylint: disable=unused-argument
- """
- Call the /api/v2/enterprise/coupons/create_refunded_voucher/ API to create new voucher and assign it to user.
- """
-
- if skip_refund:
- return
- if not course_enrollment.refundable():
- return
- if not course_enrollment.is_order_voucher_refundable():
- return
- if not EnterpriseCourseEnrollment.objects.filter(
- enterprise_customer_user__user_id=course_enrollment.user_id,
- course_id=str(course_enrollment.course.id)
- ).exists():
- return
-
- service_user = User.objects.get(username=settings.ECOMMERCE_SERVICE_WORKER_USERNAME)
-
- # TODO: Replace ecommerce_api_client with get_ecommerce_api_client after completing ENT-6112
- # https://2u-internal.atlassian.net/browse/ENT-6112
- client = ecommerce_api_client(service_user)
- order_number = course_enrollment.get_order_attribute_value('order_number')
- if order_number:
- error_message = "Encountered {} from ecommerce while creating refund voucher. Order={}, enrollment={}, user={}"
- try:
- client.enterprise.coupons.create_refunded_voucher.post({"order": order_number})
- except HttpClientError as ex:
- log.info(
- error_message.format(type(ex).__name__, order_number, course_enrollment, course_enrollment.user)
- )
- except Exception as ex: # pylint: disable=broad-except
- log.exception(
- error_message.format(type(ex).__name__, order_number, course_enrollment, course_enrollment.user)
- )
diff --git a/openedx/features/enterprise_support/tasks.py b/openedx/features/enterprise_support/tasks.py
deleted file mode 100644
index 248461a627de..000000000000
--- a/openedx/features/enterprise_support/tasks.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""
-Tasks for Enterprise.
-"""
-
-
-import logging
-
-from celery import shared_task
-from enterprise.models import EnterpriseCourseEnrollment
-
-from openedx.features.enterprise_support.utils import clear_data_consent_share_cache
-
-log = logging.getLogger('edx.celery.task')
-
-
-@shared_task(name='openedx.features.enterprise_support.tasks.clear_enterprise_customer_data_consent_share_cache')
-def clear_enterprise_customer_data_consent_share_cache(enterprise_customer_uuid):
- """
- clears data_sharing_consent_needed cache for whole enterprise
- """
- enterprise_course_enrollments = EnterpriseCourseEnrollment.objects.filter(
- enterprise_customer_user__enterprise_customer__uuid=enterprise_customer_uuid
- )
- count = enterprise_course_enrollments.count()
- log.info(
- 'Stated Clearing {count} data_sharing_consent_needed cache for enterprise customer {uuid}'.format( # noqa: UP032 # pylint: disable=line-too-long
- count=count,
- uuid=enterprise_customer_uuid,
- )
- )
- for enrollment in enterprise_course_enrollments:
- clear_data_consent_share_cache(
- enrollment.enterprise_customer_user.user_id,
- enrollment.course_id,
- enterprise_customer_uuid,
- )
- log.info('Ended Clearing data_sharing_consent_needed cache for enterprise customer {uuid}'.format( # noqa: UP032
- uuid=enterprise_customer_uuid,
- ))
diff --git a/openedx/features/enterprise_support/templates/enterprise_support/admin/enrollment_attributes_override.html b/openedx/features/enterprise_support/templates/enterprise_support/admin/enrollment_attributes_override.html
deleted file mode 100644
index 7d10a8298b67..000000000000
--- a/openedx/features/enterprise_support/templates/enterprise_support/admin/enrollment_attributes_override.html
+++ /dev/null
@@ -1,33 +0,0 @@
-{% extends "admin/base_site.html" %}
-{% load i18n static admin_urls %}
-
-{% block extrastyle %}
-
-
-{% endblock %}
-
-{% block extrahead %}
-
-{% endblock %}
-
-{% block breadcrumbs %}
-
-{% endblock %}
-
-{% block content %}
-
-
-
{% trans "Upload CSV to override enrollment attributes for learners" as tmsg %}{{ tmsg | force_escape }}
-
-
-
-{% endblock %}
diff --git a/openedx/features/enterprise_support/templates/enterprise_support/enterprise_consent_declined_notification.html b/openedx/features/enterprise_support/templates/enterprise_support/enterprise_consent_declined_notification.html
deleted file mode 100644
index a9b837a26df9..000000000000
--- a/openedx/features/enterprise_support/templates/enterprise_support/enterprise_consent_declined_notification.html
+++ /dev/null
@@ -1,15 +0,0 @@
-## mako
-
-<%page expression_filter="h"/>
-
-
diff --git a/openedx/features/enterprise_support/tests/__init__.py b/openedx/features/enterprise_support/tests/__init__.py
deleted file mode 100644
index dd037f8721f2..000000000000
--- a/openedx/features/enterprise_support/tests/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
-Things commonly needed in Enterprise tests.
-"""
-
-
-FAKE_ENTERPRISE_CUSTOMER = {
- 'active': True,
- 'branding_configuration': None,
- 'catalog': None,
- 'enable_audit_enrollment': False,
- 'enable_data_sharing_consent': False,
- 'enforce_data_sharing_consent': 'at_enrollment',
- 'enterprise_customer_entitlements': [],
- 'identity_provider': None,
- 'name': 'EnterpriseCustomer',
- 'replace_sensitive_sso_username': True,
- 'site': {'domain': 'example.com', 'name': 'example.com'},
- 'uuid': '1cbf230f-f514-4a05-845e-d57b8e29851c'
-}
diff --git a/openedx/features/enterprise_support/tests/factories.py b/openedx/features/enterprise_support/tests/factories.py
deleted file mode 100644
index f0a2887ae998..000000000000
--- a/openedx/features/enterprise_support/tests/factories.py
+++ /dev/null
@@ -1,118 +0,0 @@
-"""
-Factoryboy factories.
-"""
-
-
-from uuid import UUID
-
-import factory
-from enterprise.models import (
- EnterpriseCourseEnrollment,
- EnterpriseCustomer,
- EnterpriseCustomerBrandingConfiguration,
- EnterpriseCustomerIdentityProvider,
- EnterpriseCustomerUser,
-)
-from faker import Factory as FakerFactory
-
-from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
-
-FAKER = FakerFactory.create()
-
-
-class EnterpriseCustomerFactory(factory.django.DjangoModelFactory):
- """
- EnterpriseCustomer factory.
-
- Creates an instance of EnterpriseCustomer with minimal boilerplate - uses this class' attributes as default
- parameters for EnterpriseCustomer constructor.
- """
-
- class Meta:
- """
- Meta for EnterpriseCustomerFactory.
- """
-
- model = EnterpriseCustomer
-
- uuid = factory.LazyAttribute(lambda x: UUID(FAKER.uuid4())) # pylint: disable=no-member
- name = factory.LazyAttribute(lambda x: FAKER.company()) # pylint: disable=no-member
- slug = factory.LazyAttribute(lambda x: FAKER.slug()) # pylint: disable=no-member
- active = True
- site = factory.SubFactory(SiteFactory)
- enable_data_sharing_consent = True
- enforce_data_sharing_consent = EnterpriseCustomer.AT_ENROLLMENT
- enable_learner_portal = False
-
-
-class EnterpriseCustomerUserFactory(factory.django.DjangoModelFactory):
- """
- EnterpriseCustomer factory.
-
- Creates an instance of EnterpriseCustomerUser with minimal boilerplate - uses this class' attributes as default
- parameters for EnterpriseCustomerUser constructor.
- """
-
- class Meta:
- """
- Meta for EnterpriseCustomerFactory.
- """
-
- model = EnterpriseCustomerUser
-
- enterprise_customer = factory.SubFactory(EnterpriseCustomerFactory)
- user_id = factory.LazyAttribute(lambda x: FAKER.pyint()) # pylint: disable=no-member
-
-
-class EnterpriseCourseEnrollmentFactory(factory.django.DjangoModelFactory):
- """
- EnterpriseCourseEnrollment factory.
-
- Creates an instance of EnterpriseCourseEnrollment with minimal boilerplate.
- """
-
- class Meta:
- """
- Meta for EnterpriseCourseEnrollmentFactory.
- """
-
- model = EnterpriseCourseEnrollment
-
- course_id = factory.LazyAttribute(lambda x: FAKER.slug()) # pylint: disable=no-member
- enterprise_customer_user = factory.SubFactory(EnterpriseCustomerUserFactory)
-
-
-class EnterpriseCustomerBrandingConfigurationFactory(factory.django.DjangoModelFactory):
- """
- EnterpriseCustomerBrandingConfiguration factory
-
- Creates an instance of EnterpriseCustomerBrandingConfiguration with minimal boilerplate.
- """
-
- class Meta:
- """
- Meta for EnterpriseCustomerBrandingConfigurationFactory.
- """
-
- model = EnterpriseCustomerBrandingConfiguration
-
- logo = FAKER.image_url() # pylint: disable=no-member
- primary_color = FAKER.color() # pylint: disable=no-member
- secondary_color = FAKER.color() # pylint: disable=no-member
- tertiary_color = FAKER.color() # pylint: disable=no-member
-
-
-class EnterpriseCustomerIdentityProviderFactory(factory.django.DjangoModelFactory):
- """
- EnterpriseCustomerIdentityProvider factory.
- """
-
- class Meta:
- """
- Meta for EnterpriseCustomerIdentityProviderFactory.
- """
-
- model = EnterpriseCustomerIdentityProvider
-
- enterprise_customer = factory.SubFactory(EnterpriseCustomerFactory)
- provider_id = factory.LazyAttribute(lambda x: FAKER.slug()) # pylint: disable=no-member
diff --git a/openedx/features/enterprise_support/tests/mixins/__init__.py b/openedx/features/enterprise_support/tests/mixins/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/openedx/features/enterprise_support/tests/mixins/enterprise.py b/openedx/features/enterprise_support/tests/mixins/enterprise.py
deleted file mode 100644
index e16b95e619ec..000000000000
--- a/openedx/features/enterprise_support/tests/mixins/enterprise.py
+++ /dev/null
@@ -1,279 +0,0 @@
-""" # pylint: disable=cyclic-import
-Mixins for the EnterpriseApiClient.
-"""
-
-
-import json
-
-import httpretty
-from django.conf import settings
-from django.core.cache import cache
-
-
-class EnterpriseServiceMockMixin:
- """
- Mocks for the Enterprise service responses.
- """
-
- consent_url = '{}{}'.format(settings.ENTERPRISE_CONSENT_API_URL, 'data_sharing_consent')
-
- def setUp(self):
- super().setUp()
- cache.clear()
-
- @staticmethod
- def get_enterprise_url(path):
- """Return a URL to the configured Enterprise API. """
- return f'{settings.ENTERPRISE_API_URL}{path}/'
-
- def mock_get_enterprise_customer(self, uuid, response, status):
- """
- Helper to mock the HTTP call to the /enterprise-customer/uuid endpoint
- """
- body = json.dumps(response)
- httpretty.register_uri(
- method=httpretty.GET,
- uri=(self.get_enterprise_url('enterprise-customer') + uuid + '/'),
- body=body,
- content_type='application/json',
- status=status,
- )
-
- def mock_enterprise_course_enrollment_post_api( # pylint: disable=invalid-name
- self,
- username='test_user',
- course_id='course-v1:edX+DemoX+Demo_Course',
- consent_granted=True
- ):
- """
- Helper method to register the enterprise course enrollment API POST endpoint.
- """
- api_response = {
- username: username,
- course_id: course_id,
- consent_granted: consent_granted,
- }
- api_response_json = json.dumps(api_response)
- httpretty.register_uri(
- method=httpretty.POST,
- uri=self.get_enterprise_url('enterprise-course-enrollment'),
- body=api_response_json,
- content_type='application/json'
- )
-
- def mock_enterprise_course_enrollment_post_api_failure(self): # pylint: disable=invalid-name
- """
- Helper method to register the enterprise course enrollment API endpoint for a failure.
- """
- httpretty.register_uri(
- method=httpretty.POST,
- uri=self.get_enterprise_url('enterprise-course-enrollment'),
- body='{}',
- content_type='application/json',
- status=500
- )
-
- def mock_consent_response( # pylint: disable=missing-function-docstring
- self,
- username,
- course_id,
- ec_uuid,
- method=httpretty.GET,
- granted=True,
- required=False,
- exists=True,
- response_code=None
- ):
- response_body = {
- 'username': username,
- 'course_id': course_id,
- 'enterprise_customer_uuid': ec_uuid,
- 'consent_provided': granted,
- 'consent_required': required,
- 'exists': exists,
- }
- httpretty.register_uri(
- method=method,
- uri=self.consent_url,
- content_type='application/json',
- body=json.dumps(response_body),
- status=response_code or 200,
- )
-
- def mock_consent_post(self, username, course_id, ec_uuid):
- self.mock_consent_response(
- username,
- course_id,
- ec_uuid,
- method=httpretty.POST,
- granted=True,
- exists=True,
- )
-
- def mock_consent_get(self, username, course_id, ec_uuid):
- self.mock_consent_response(
- username,
- course_id,
- ec_uuid
- )
-
- def mock_consent_missing(self, username, course_id, ec_uuid):
- self.mock_consent_response(
- username,
- course_id,
- ec_uuid,
- exists=False,
- granted=False,
- required=True,
- )
-
- def mock_consent_not_required(self, username, course_id, ec_uuid):
- self.mock_consent_response(
- username,
- course_id,
- ec_uuid,
- exists=False,
- granted=False,
- required=False,
- )
-
- def get_mock_active_enterprise_learner_details(
- self,
- learner_id=1,
- enterprise_customer_uuid='cf246b88-d5f6-4908-a522-fc307e0b0c59',
- enable_audit_enrollment=False,
- enable_data_sharing_consent=True,
- slug='test-shib',
- data_sharing_consent_records=None,
- site_domain='example.com',
- ):
- """
- Helper function to format enterprise learner API response.
- """
- mock_result = {
- 'id': learner_id,
- 'enterprise_customer': {
- 'uuid': enterprise_customer_uuid,
- 'name': 'TestShib',
- 'slug': slug,
- 'active': True,
- 'site': {
- 'domain': site_domain,
- 'name': 'example.com'
- },
- 'enable_data_sharing_consent': enable_data_sharing_consent,
- 'enforce_data_sharing_consent': 'at_login',
- 'enable_audit_enrollment': enable_audit_enrollment,
- },
- 'active': True,
- 'user_id': learner_id,
- 'user': {
- 'id': learner_id,
- 'username': 'verified',
- 'first_name': '',
- 'last_name': '',
- 'email': 'verified@example.com',
- 'is_staff': True,
- 'is_active': True,
- 'date_joined': '2016-09-01T19:18:26.026495Z'
- },
- 'data_sharing_consent_records': data_sharing_consent_records or [],
- 'groups': [],
- 'created': '2016-09-01T19:18:26.026495Z',
- 'invite_key': '',
- 'role_assignments': [
- 'enterprise_learner'
- ]
- }
- return mock_result
-
- def get_mock_enterprise_learner_results(
- self,
- entitlement_id=1,
- learner_id=1,
- enterprise_customer_uuid='cf246b88-d5f6-4908-a522-fc307e0b0c59',
- enable_audit_enrollment=False,
- ):
- """
- Helper function to format enterprise learner API response.
- """
- mock_results = [
- {
- 'id': learner_id,
- 'enterprise_customer': {
- 'uuid': enterprise_customer_uuid,
- 'name': 'TestShib',
- 'active': True,
- 'site': {
- 'domain': 'example.com',
- 'name': 'example.com'
- },
- 'enable_data_sharing_consent': True,
- 'enforce_data_sharing_consent': 'at_login',
- 'enable_audit_enrollment': enable_audit_enrollment,
- 'branding_configuration': {
- 'enterprise_customer': enterprise_customer_uuid,
- 'logo': 'https://open.edx.org/sites/all/themes/edx_open/logo.png'
- },
- 'enterprise_customer_entitlements': [
- {
- 'enterprise_customer': enterprise_customer_uuid,
- 'entitlement_id': entitlement_id
- }
- ],
- 'replace_sensitive_sso_username': True,
- },
- 'user_id': 5,
- 'user': {
- 'username': 'verified',
- 'first_name': '',
- 'last_name': '',
- 'email': 'verified@example.com',
- 'is_staff': True,
- 'is_active': True,
- 'date_joined': '2016-09-01T19:18:26.026495Z'
- },
- 'data_sharing_consent': [
- {
- "username": "verified",
- "enterprise_customer_uuid": enterprise_customer_uuid,
- "exists": True,
- "course_id": "course-v1:edX DemoX Demo_Course",
- "consent_provided": True,
- "consent_required": False
- }
- ]
- }
- ]
- return mock_results
-
- def mock_enterprise_learner_api(
- self,
- entitlement_id=1,
- learner_id=1,
- enterprise_customer_uuid='cf246b88-d5f6-4908-a522-fc307e0b0c59',
- enable_audit_enrollment=False,
- ):
- """
- Helper function to register enterprise learner API endpoint.
- """
- results = self.get_mock_enterprise_learner_results(
- entitlement_id, learner_id, enterprise_customer_uuid, enable_audit_enrollment
- )
- enterprise_learner_api_response = {
- 'count': 1,
- 'num_pages': 1,
- 'current_page': 1,
- 'results': results,
- 'next': None,
- 'start': 0,
- 'previous': None
- }
- enterprise_learner_api_response_json = json.dumps(enterprise_learner_api_response)
-
- httpretty.register_uri(
- method=httpretty.GET,
- uri=self.get_enterprise_url('enterprise-learner'),
- body=enterprise_learner_api_response_json,
- content_type='application/json'
- )
diff --git a/openedx/features/enterprise_support/tests/test_admin.py b/openedx/features/enterprise_support/tests/test_admin.py
deleted file mode 100644
index be80dc022ee0..000000000000
--- a/openedx/features/enterprise_support/tests/test_admin.py
+++ /dev/null
@@ -1,149 +0,0 @@
-"""
-Enterprise support admin tests.
-"""
-
-import csv
-import os
-import tempfile
-
-from django.contrib.messages import get_messages
-from django.test import Client
-from django.urls import reverse
-
-from common.djangoapps.student.models import CourseEnrollment, CourseEnrollmentAttribute
-from common.djangoapps.student.tests.factories import TEST_PASSWORD, AdminFactory, CourseEnrollmentFactory, UserFactory
-from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory
-from openedx.features.enterprise_support.admin.forms import CSVImportForm
-from xmodule.modulestore.tests.django_utils import (
- ModuleStoreTestCase, # pylint: disable=wrong-import-order
-)
-
-
-class EnrollmentAttributeOverrideViewTest(ModuleStoreTestCase):
- """
- Tests for course creator admin.
- """
-
- def setUp(self):
- """ Test case setup """
- super().setUp()
-
- self.client = Client()
- user = AdminFactory()
- self.view_url = reverse('admin:enterprise_override_attributes')
- self.client.login(username=user.username, password=TEST_PASSWORD)
-
- self.users = []
- for _ in range(3):
- self.users.append(UserFactory())
-
- self.course = CourseRunFactory()
- self.course_id = self.course.get('key') # pylint: disable=no-member
- self.csv_data = [
- [self.users[0].id, self.course_id, 'OP_4321'],
- [self.users[1].id, self.course_id, 'OP_8765'],
- [self.users[2].id, self.course_id, 'OP_2109'],
- ]
- self.csv_data_for_existing_attributes = [
- [self.users[0].id, self.course_id, 'OP_1234'],
- [self.users[1].id, self.course_id, 'OP_5678'],
- [self.users[2].id, self.course_id, 'OP_9012'],
- ]
-
- for user in self.users:
- CourseEnrollmentFactory(
- course_id=self.course_id,
- user=user
- )
-
- def create_csv(self, header=None, data=None):
- """Create csv"""
- header = header or ['lms_user_id', 'course_id', 'opportunity_id']
- data = data or self.csv_data
- tmp_csv_path = os.path.join(tempfile.gettempdir(), 'data.csv')
- with open(tmp_csv_path, 'w') as csv_file:
- csv_writer = csv.writer(csv_file)
- csv_writer.writerow(header)
- csv_writer.writerows(data)
-
- return tmp_csv_path
-
- def verify_enrollment_attributes(self, data=None):
- """
- Verify that data from csv is imported correctly and tables have correct data.
- """
- data = data or self.csv_data
- for user_id, course_id, opportunity_id in data:
- enrollment = CourseEnrollment.objects.get(user_id=user_id, course_id=course_id)
- enrollment_attribute = CourseEnrollmentAttribute.objects.get(
- enrollment=enrollment,
- namespace='salesforce',
- name='opportunity_id'
- )
- assert enrollment_attribute.value == opportunity_id
-
- def test_get(self):
- """
- Tests that HTTP GET is working as expected.
- """
- response = self.client.get(self.view_url)
- assert response.status_code == 200
- assert isinstance(response.context['form'], CSVImportForm)
-
- def test_post(self):
- """
- Tests that HTTP POST is working as expected when creating new attributes and updating.
- """
- csv_path = self.create_csv()
- with open(csv_path) as csv_file:
- post_data = {'csv_file': csv_file}
- response = self.client.post(self.view_url, data=post_data)
- assert response.status_code == 302
- self.verify_enrollment_attributes()
-
- # override existing
- csv_path = self.create_csv(data=self.csv_data_for_existing_attributes)
- with open(csv_path) as csv_file:
- post_data = {'csv_file': csv_file}
- response = self.client.post(self.view_url, data=post_data)
- assert response.status_code == 302
- self.verify_enrollment_attributes(data=self.csv_data_for_existing_attributes)
-
- def test_post_with_no_csv(self):
- """
- Tests that HTTP POST without out csv file is working as expected.
- """
- response = self.client.post(self.view_url)
- assert response.context['form'].errors == {'csv_file': ['This field is required.']}
-
- def test_post_with_incorrect_csv_header(self):
- """
- Tests that HTTP POST with incorrect csv header is working as expected.
- """
- csv_path = self.create_csv(header=['a', 'b'])
- post_data = {'csv_file': open(csv_path)}
- response = self.client.post(self.view_url, data=post_data)
- assert response.context['form'].errors == {
- 'csv_file': [
- 'Expected a CSV file with [lms_user_id, course_id, opportunity_id] '
- 'columns, but found [a, b] columns instead.'
- ]
- }
-
- def test_post_with_no_enrollment_error(self):
- """
- Tests that HTTP POST is working as expected when for some records there is no enrollment.
- """
- csv_data = self.csv_data + [[999, self.course_id, 'NOPE'], [1000, self.course_id, 'NONE']]
- csv_path = self.create_csv(data=csv_data)
- post_data = {'csv_file': open(csv_path)}
- response = self.client.post(self.view_url, data=post_data)
- assert response.status_code == 302
- messages = []
- for msg in get_messages(response.wsgi_request):
- messages.append(str(msg))
- assert messages == [
- 'Successfully updated learner enrollment opportunity ids.',
- 'Enrollment attributes were not updated for records at following line numbers '
- 'in csv because no enrollment found for these records: 4, 5'
- ]
diff --git a/openedx/features/enterprise_support/tests/test_api.py b/openedx/features/enterprise_support/tests/test_api.py
deleted file mode 100644
index e1008cce2054..000000000000
--- a/openedx/features/enterprise_support/tests/test_api.py
+++ /dev/null
@@ -1,860 +0,0 @@
-"""
-Test the enterprise support APIs.
-"""
-from unittest import mock
-
-import ddt
-import httpretty
-import pytest
-from consent.models import DataSharingConsent
-from django.conf import settings
-from django.contrib.auth.models import User # pylint: disable=imported-auth-user
-from django.core.cache import cache
-from django.test.utils import override_settings
-from edx_django_utils.cache import get_cache_key
-from requests.exceptions import HTTPError
-
-from common.djangoapps.student.tests.factories import UserFactory
-from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
-from openedx.features.enterprise_support.api import (
- _CACHE_MISS,
- ENTERPRISE_CUSTOMER_KEY_NAME,
- ConsentApiClient,
- ConsentApiServiceClient,
- EnterpriseApiClient,
- EnterpriseApiException,
- EnterpriseApiServiceClient,
- activate_learner_enterprise,
- add_enterprise_customer_to_session,
- enterprise_customer_for_request,
- enterprise_customer_from_api,
- enterprise_customer_from_session,
- enterprise_customer_from_session_or_learner_data,
- enterprise_customer_uuid_for_request,
- enterprise_enabled,
- get_consent_notification_data,
- get_dashboard_consent_notification,
- get_data_sharing_consents,
- get_enterprise_course_enrollments,
- get_enterprise_learner_data_from_api,
- get_enterprise_learner_data_from_db,
- get_enterprise_learner_portal_enabled_message,
-)
-from openedx.features.enterprise_support.tests.factories import (
- EnterpriseCourseEnrollmentFactory,
- EnterpriseCustomerIdentityProviderFactory,
- EnterpriseCustomerUserFactory,
-)
-from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin
-
-
-class MockEnrollment(mock.MagicMock):
- """
- Mock object for an enrollment which has a consistent string representation
- suitable for use in ddt parameters.
- """
- def __repr__(self):
- return ''.format(getattr(self, 'course_id', None))
-
-
-@ddt.ddt
-@override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
-@skip_unless_lms
-class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
- """
- Test enterprise support APIs.
- """
- ENABLED_CACHES = ['default']
-
- @classmethod
- def setUpTestData(cls):
- cls.user = UserFactory.create(
- username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME,
- email='ent_worker@example.com',
- password='password123',
- )
- super().setUpTestData()
-
- def _assert_api_service_client(self, api_client, mocked_jwt_builder):
- """
- Verify that the provided api client uses the enterprise service user to generate
- JWT token for auth.
- """
- mocked_jwt_builder.return_value = 'test-token'
- enterprise_service_user = User.objects.get(username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME)
- enterprise_api_service_client = api_client()
-
- mocked_jwt_builder.assert_called_once_with(enterprise_service_user)
- # pylint: disable=protected-access
- assert enterprise_api_service_client.client.auth.token == 'test-token'
-
- def _assert_api_client_with_user(self, api_client, mocked_jwt_builder):
- """
- Verify that the provided api client uses the expected user to generate
- JWT token for auth.
- """
- mocked_jwt_builder.return_value = 'test-token'
- dummy_enterprise_user = UserFactory.create(
- username='dummy-enterprise-user',
- email='dummy-enterprise-user@example.com',
- password='password123',
- )
- enterprise_api_service_client = api_client(dummy_enterprise_user)
-
- mocked_jwt_builder.assert_called_once_with(dummy_enterprise_user)
- # pylint: disable=protected-access
- assert enterprise_api_service_client.client.auth.token == 'test-token'
- return enterprise_api_service_client
-
- def _assert_get_enterprise_customer(self, api_client, enterprise_api_data_for_mock):
- """
- DRY method to verify caching for get enterprise customer method.
- """
- cache_key = get_cache_key(
- resource='enterprise-customer',
- resource_id=enterprise_api_data_for_mock['uuid'],
- username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME,
- )
- self.mock_get_enterprise_customer(enterprise_api_data_for_mock['uuid'], enterprise_api_data_for_mock, 200)
- self._assert_get_enterprise_customer_with_cache(api_client, enterprise_api_data_for_mock, cache_key)
-
- def _assert_get_enterprise_customer_with_cache(self, api_client, enterprise_customer_data, cache_key):
- """
- DRY method to verify that get enterprise customer response is cached.
- """
- cached_enterprise_customer = cache.get(cache_key)
- assert cached_enterprise_customer is None
-
- enterprise_customer = api_client.get_enterprise_customer(enterprise_customer_data['uuid'])
- assert enterprise_customer_data == enterprise_customer
- cached_enterprise_customer = cache.get(cache_key)
- assert cached_enterprise_customer == enterprise_customer
-
- @httpretty.activate
- @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user')
- def test_enterprise_api_client_with_service_user(self, mock_jwt_builder):
- """
- Verify that enterprise API service client uses enterprcreate_jwt_for_userise service user
- by default to authenticate and access enterprise API.
- """
- self._assert_api_service_client(EnterpriseApiServiceClient, mock_jwt_builder)
-
- # Verify that enterprise customer data is cached properly for the
- # enterprise api client.
- enterprise_api_client = EnterpriseApiServiceClient()
- enterprise_api_data_for_mock_1 = {'name': 'dummy-enterprise-customer-1', 'uuid': 'enterprise-uuid-1'}
- self._assert_get_enterprise_customer(enterprise_api_client, enterprise_api_data_for_mock_1)
-
- # Now try to get enterprise customer for another enterprise and verify
- # that enterprise api client returns data according to the provided
- # enterprise UUID.
- enterprise_api_data_for_mock_2 = {'name': 'dummy-enterprise-customer-2', 'uuid': 'enterprise-uuid-2'}
- self._assert_get_enterprise_customer(enterprise_api_client, enterprise_api_data_for_mock_2)
-
- @httpretty.activate
- @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user')
- def test_enterprise_api_client_with_user(self, mock_jwt_builder):
- """
- Verify that enterprise API client uses the provided user to
- authenticate and access enterprise API.
- """
- self._assert_api_client_with_user(EnterpriseApiClient, mock_jwt_builder)
-
- @ddt.data(True, False)
- @httpretty.activate
- @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user')
- def test_enterprise_api_client_with_user_post_enrollment(self, should_raise_http_error, mock_jwt_builder):
- """
- Verify that enterprise API client uses the provided user to
- authenticate and access enterprise API.
- """
- api_client = self._assert_api_client_with_user(EnterpriseApiClient, mock_jwt_builder)
- mock_client = mock.Mock()
- api_client.client = mock_client
- if should_raise_http_error:
- mock_client.post.side_effect = HTTPError
-
- username = 'spongebob'
- course_id = 'burger-flipping-101'
-
- if should_raise_http_error:
- with pytest.raises(EnterpriseApiException):
- api_client.post_enterprise_course_enrollment(username, course_id)
- else:
- api_client.post_enterprise_course_enrollment(username, course_id)
-
- mock_client.post.assert_called_once_with(
- f"{api_client.base_api_url}enterprise-course-enrollment/",
- data={
- 'username': username,
- 'course_id': course_id,
- }
- )
-
- @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_uuid_for_request')
- @mock.patch('openedx.features.enterprise_support.api.EnterpriseApiClient')
- def test_enterprise_customer_from_api_cache_miss(self, mock_client_class, mock_uuid_from_request):
- mock_uuid_from_request.return_value = _CACHE_MISS
- mock_request = mock.Mock()
-
- actual_result = enterprise_customer_from_api(mock_request)
- assert actual_result is None
- assert not mock_client_class.called
-
- @httpretty.activate
- @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user')
- def test_enterprise_consent_api_client_with_service_user(self, mock_jwt_builder):
- """
- Verify that enterprise API consent service client uses enterprise
- service user by default to authenticate and access enterprise API.
- """
- self._assert_api_service_client(ConsentApiServiceClient, mock_jwt_builder)
-
- @httpretty.activate
- @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user')
- def test_enterprise_consent_api_client_with_user(self, mock_jwt_builder):
- """
- Verify that enterprise API consent service client uses the provided
- user to authenticate and access enterprise API.
- """
- consent_client = self._assert_api_client_with_user(ConsentApiClient, mock_jwt_builder)
- mock_client = mock.Mock()
- consent_client.client = mock_client
-
- kwargs = {
- 'foo': 'a',
- 'bar': 'b',
- }
- consent_client.provide_consent(**kwargs)
- consent_client.revoke_consent(**kwargs)
-
- mock_client.post.assert_called_once_with(consent_client.consent_endpoint, json=kwargs)
- mock_client.delete.assert_called_once_with(consent_client.consent_endpoint, json=kwargs)
-
- @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user')
- def test_fetch_enterprise_learner_data_unauthenticated(self, mock_jwt_builder):
- api_client = self._assert_api_client_with_user(EnterpriseApiClient, mock_jwt_builder)
- setattr(api_client.client, 'enterprise-learner', mock.Mock())
- mock_endpoint = getattr(api_client.client, 'enterprise-learner')
-
- user = mock.Mock(is_authenticated=False)
- assert api_client.fetch_enterprise_learner_data(user) is None
-
- assert not mock_endpoint.called
-
- @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user')
- def test_fetch_enterprise_learner_data(self, mock_jwt_builder):
- """
- Test EnterpriseApiClient's fetch_enterprise_learner_data method.
- """
- api_client = self._assert_api_client_with_user(EnterpriseApiClient, mock_jwt_builder)
- mock_client = mock.Mock()
- api_client.client = mock_client
-
- user = mock.Mock(is_authenticated=True, username='spongebob')
- response = api_client.fetch_enterprise_learner_data(user)
-
- assert mock_client.get.return_value.json.return_value == response
- mock_client.get.assert_called_once_with(
- f"{api_client.base_api_url}enterprise-learner/",
- params={'username': user.username},
- )
-
- @mock.patch('openedx.features.enterprise_support.api.get_current_request')
- @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user')
- def test_fetch_enterprise_learner_data_http_error(self, mock_jwt_builder, mock_get_current_request):
- """
- Test error handling for the EnterpriseApiClient's fetch_enterprise_learner_data method.
- """
- api_client = self._assert_api_client_with_user(EnterpriseApiClient, mock_jwt_builder)
-
- mock_client = mock.Mock()
- mock_client.get.side_effect = HTTPError
- api_client.client = mock_client
- mock_get_current_request.return_value.META = {
- 'PATH_INFO': 'whatever',
- }
-
- user = mock.Mock(is_authenticated=True, username='spongebob')
-
- assert api_client.fetch_enterprise_learner_data(user) is None
- url = f"{api_client.base_api_url}enterprise-learner/"
- mock_client.get.assert_called_once_with(url, params={'username': user.username})
-
- @mock.patch('openedx.features.enterprise_support.api.EnterpriseApiClient')
- def test_get_enterprise_learner_data_from_api(self, mock_api_client_class):
- user = mock.Mock(is_authenticated=True)
- mock_client = mock_api_client_class.return_value
- mock_client.fetch_enterprise_learner_data.return_value = {
- 'results': 'the-learner-data',
- }
-
- learner_data = get_enterprise_learner_data_from_api(user)
-
- assert 'the-learner-data' == learner_data
- mock_api_client_class.assert_called_once_with(user=user)
- mock_client.fetch_enterprise_learner_data.assert_called_once_with(user)
-
- def test_activate_learner_enterprise(self):
- """
- Test enterprise is activated successfully for user
- """
- request_mock = mock.MagicMock(session={}, user=self.user)
- enterprise_customer_user = EnterpriseCustomerUserFactory(user_id=self.user.id)
- enterprise_customer_uuid = enterprise_customer_user.enterprise_customer.uuid
-
- activate_learner_enterprise(request_mock, self.user, enterprise_customer_uuid)
- assert request_mock.session['enterprise_customer']['uuid'] == str(enterprise_customer_uuid)
-
- def test_get_enterprise_learner_data_from_db_no_data(self):
- assert not get_enterprise_learner_data_from_db(self.user)
-
- def test_get_enterprise_learner_data_from_db(self):
- EnterpriseCustomerUserFactory(user_id=self.user.id)
- user_data = get_enterprise_learner_data_from_db(self.user)[0]['user']
- assert user_data['username'] == self.user.username
-
- @ddt.data(True, False)
- @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled')
- def test_get_data_sharing_consents(self, is_enterprise_enabled, mock_enterprise_enabled):
- mock_enterprise_enabled.return_value = is_enterprise_enabled
- enterprise_customer_user = EnterpriseCustomerUserFactory(user_id=self.user.id)
-
- if not is_enterprise_enabled:
- assert get_data_sharing_consents(self.user) == []
- else:
- course_id = 'fake-course'
- data_sharing_consent = DataSharingConsent(
- course_id=course_id,
- enterprise_customer=enterprise_customer_user.enterprise_customer,
- username=self.user.username,
- granted=False
- )
- data_sharing_consent.save()
- data_sharing_consents = get_data_sharing_consents(self.user)
- assert len(data_sharing_consents) == 1
- assert data_sharing_consents[0].id == data_sharing_consent.id
-
- @ddt.data(True, False)
- @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled')
- def test_get_enterprise_course_enrollments(self, is_enterprise_enabled, mock_enterprise_enabled):
- mock_enterprise_enabled.return_value = is_enterprise_enabled
- enterprise_customer_user = EnterpriseCustomerUserFactory(user_id=self.user.id)
-
- if not is_enterprise_enabled:
- assert get_enterprise_course_enrollments(self.user) == []
- else:
- ece = EnterpriseCourseEnrollmentFactory(enterprise_customer_user=enterprise_customer_user)
- enterprise_course_enrollments = get_enterprise_course_enrollments(self.user)
- assert len(enterprise_course_enrollments) == 1
- assert enterprise_course_enrollments[0].id == ece.id
-
- @httpretty.activate
- @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db')
- @mock.patch('openedx.features.enterprise_support.api.EnterpriseCustomer')
- @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline')
- @mock.patch('openedx.features.enterprise_support.api.Registry')
- def test_enterprise_customer_for_request(
- self,
- mock_registry,
- mock_partial,
- mock_enterprise_customer_model,
- mock_get_enterprise_learner_data,
- ):
- def mock_get_enterprise_customer(**kwargs):
- uuid = kwargs.get('enterprise_customer_identity_providers__provider_id')
- if uuid:
- return mock.MagicMock(uuid=uuid, user=self.user)
- raise Exception
-
- dummy_request = mock.MagicMock(session={}, user=self.user)
- mock_enterprise_customer_model.objects.get.side_effect = mock_get_enterprise_customer
- mock_enterprise_customer_model.DoesNotExist = Exception
- mock_partial.return_value = True
- mock_registry.get_from_pipeline.return_value.provider_id = 'real-ent-uuid'
-
- # Verify that the method `enterprise_customer_for_request` returns
- # expected enterprise customer against the requesting user.
- self.mock_get_enterprise_customer('real-ent-uuid', {'real': 'enterprisecustomer'}, 200)
- enterprise_customer = enterprise_customer_for_request(dummy_request)
- assert enterprise_customer == {'real': 'enterprisecustomer'}
-
- httpretty.reset()
-
- # Verify that the method `enterprise_customer_for_request` returns no
- # enterprise customer if the enterprise customer API throws 404.
- del dummy_request.session['enterprise_customer']
- self.mock_get_enterprise_customer('real-ent-uuid', {'detail': 'Not found.'}, 404)
- enterprise_customer = enterprise_customer_for_request(dummy_request)
- assert enterprise_customer is None
-
- httpretty.reset()
-
- # Verify that the method `enterprise_customer_for_request` returns
- # expected enterprise customer against the requesting user even if
- # the third-party auth pipeline has no `provider_id`.
- mock_registry.get_from_pipeline.return_value.provider_id = None
- self.mock_get_enterprise_customer('real-ent-uuid', {'real': 'enterprisecustomer'}, 200)
- mock_request = mock.MagicMock(
- GET={'enterprise_customer': 'real-ent-uuid'},
- COOKIES={},
- session={},
- user=self.user
- )
- enterprise_customer = enterprise_customer_for_request(mock_request)
- assert enterprise_customer == {'real': 'enterprisecustomer'}
-
- # Verify that the method `enterprise_customer_for_request` returns
- # expected enterprise customer against the requesting user even if
- # the third-party auth pipeline has no `provider_id` but there is
- # enterprise customer UUID in the cookie.
- mock_request = mock.MagicMock(
- GET={},
- COOKIES={settings.ENTERPRISE_CUSTOMER_COOKIE_NAME: 'real-ent-uuid'},
- session={},
- user=self.user
- )
- enterprise_customer = enterprise_customer_for_request(mock_request)
- assert enterprise_customer == {'real': 'enterprisecustomer'}
-
- # Verify that the method `enterprise_customer_for_request` returns
- # expected enterprise customer against the requesting user if
- # data is cached only in the request session
- mock_registry.get_from_pipeline.return_value.provider_id = None
- self.mock_get_enterprise_customer('real-ent-uuid', {'real': 'enterprisecustomer'}, 200)
- mock_request = mock.MagicMock(
- GET={},
- COOKIES={},
- session={'enterprise_customer': {'real': 'enterprisecustomer'}},
- user=self.user
- )
- enterprise_customer = enterprise_customer_for_request(mock_request)
- assert enterprise_customer == {'real': 'enterprisecustomer'}
-
- # Verify that we can still get enterprise customer from enterprise
- # learner API even if we are unable to get it from preferred sources,
- # e.g. url query parameters, third-party auth pipeline, enterprise
- # cookie, or session.
- mock_get_enterprise_learner_data.return_value = [{'enterprise_customer': {'uuid': 'real-ent-uuid'}}]
- mock_request = mock.MagicMock(
- GET={},
- COOKIES={},
- session={},
- user=self.user,
- site=1
- )
- enterprise_customer = enterprise_customer_for_request(mock_request)
- assert enterprise_customer == {'real': 'enterprisecustomer'}
-
- def test_enterprise_customer_for_request_with_session(self):
- """
- Verify enterprise_customer_for_request stores and retrieves data from session appropriately
- """
- dummy_request = mock.MagicMock(session={}, user=self.user)
- enterprise_data = {'name': 'dummy-enterprise-customer', 'uuid': '8dc65e66-27c9-447b-87ff-ede6d66e3a5d'}
-
- # Verify enterprise customer data fetched from API when it is not available in session
- with mock.patch(
- 'openedx.features.enterprise_support.api.enterprise_customer_from_api',
- return_value=enterprise_data
- ):
- assert dummy_request.session.get('enterprise_customer') is None
- enterprise_customer = enterprise_customer_for_request(dummy_request)
- assert enterprise_customer == enterprise_data
- assert dummy_request.session.get('enterprise_customer') == enterprise_data
-
- # Verify enterprise customer data fetched from session for subsequent calls
- with mock.patch(
- 'openedx.features.enterprise_support.api.enterprise_customer_from_api',
- return_value=enterprise_data
- ) as mock_enterprise_customer_from_api, mock.patch(
- 'openedx.features.enterprise_support.api.enterprise_customer_from_session',
- return_value=enterprise_data
- ) as mock_enterprise_customer_from_session:
- enterprise_customer = enterprise_customer_for_request(dummy_request)
- assert enterprise_customer == enterprise_data
- assert mock_enterprise_customer_from_api.called is False
- assert mock_enterprise_customer_from_session.called is True
-
- # Verify enterprise customer data fetched from session for subsequent calls
- # with unauthenticated user in SAML case
- del dummy_request.user
-
- with mock.patch(
- 'openedx.features.enterprise_support.api.enterprise_customer_from_api',
- return_value=enterprise_data
- ) as mock_enterprise_customer_from_api, mock.patch(
- 'openedx.features.enterprise_support.api.enterprise_customer_from_session',
- return_value=enterprise_data
- ) as mock_enterprise_customer_from_session:
- enterprise_customer = enterprise_customer_for_request(dummy_request)
- assert enterprise_customer == enterprise_data
- assert mock_enterprise_customer_from_api.called is False
- assert mock_enterprise_customer_from_session.called is True
-
- @ddt.data(
- (False, {'real': 'enterprise', 'uuid': ''}, 'course', [], [], "", ""),
- (True, {}, 'course', [], [], "", ""),
- (True, {'real': 'enterprise'}, None, [], [], "", ""),
- (True, {'name': 'GriffCo', 'uuid': ''}, 'real-course', [], [], "", ""),
- (True, {'name': 'GriffCo', 'uuid': ''}, 'real-course', [MockEnrollment(course_id='other-id')], [], "", ""),
- (
- True,
- {'name': 'GriffCo', 'uuid': 'real-uuid'},
- 'real-course',
- [
- MockEnrollment(
- course_id='real-course',
- course_overview=mock.MagicMock(
- display_name='My Cool Course'
- )
- )
- ],
- [
- 'If you have concerns about sharing your data, please contact your administrator at GriffCo.',
- 'Enrollment in My Cool Course was not complete.'
- ],
- "", ""
- ),
- (
- True,
- {'name': 'GriffCo', 'uuid': 'real-uuid'},
- 'real-course',
- [
- MockEnrollment(
- course_id='real-course',
- course_overview=mock.MagicMock(
- display_name='My Cool Course'
- )
- )
- ],
- [
- 'If you have concerns about sharing your data, please contact your administrator at GriffCo.',
- 'Enrollment in My Cool Course was not complete.'
- ],
- "Title from DataSharingConsentTextOverrides model in consent app",
- "Message from DataSharingConsentTextOverrides model in consent app"
- ),
-
- )
- @ddt.unpack
- @mock.patch('openedx.features.enterprise_support.api.ConsentApiClient')
- @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_for_request')
- @mock.patch('openedx.features.enterprise_support.api.get_consent_notification_data')
- def test_get_dashboard_consent_notification(
- self,
- consent_return_value,
- enterprise_customer,
- course_id,
- enrollments,
- expected_substrings,
- notification_title,
- notification_message,
- consent_notification_data,
- ec_for_request,
- consent_client_class
- ):
- request = mock.MagicMock(
- GET={'consent_failed': course_id}
- )
- consent_notification_data.return_value = notification_title, notification_message
- consent_client = consent_client_class.return_value
- consent_client.consent_required.return_value = consent_return_value
-
- ec_for_request.return_value = enterprise_customer
-
- user = mock.MagicMock()
-
- notification_string = get_dashboard_consent_notification(
- request, user, enrollments,
- )
-
- if notification_message and notification_title:
- assert notification_title in notification_string
- assert notification_message in notification_string
- elif expected_substrings:
- for substr in expected_substrings:
- assert substr in notification_string
- else:
- assert notification_string == ''
-
- @override_settings(ENABLE_ENTERPRISE_INTEGRATION=False)
- def test_utils_with_enterprise_disabled(self):
- """
- Test that disabling the enterprise integration flag causes
- the utilities to return the expected default values.
- """
- assert not enterprise_enabled()
-
- @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db')
- def test_enterprise_customer_from_session_or_db_cache_miss_no_customer(self, mock_learner_data_from_db):
- """
- When no customer data exists in the request session _and_
- no customer is associated with the requesting user, then ``enterprise_customer_from_session_or_learner_data()``
- should return None.
- """
- mock_request = mock.Mock(session={})
- mock_learner_data_from_db.return_value = None
-
- actual_result = enterprise_customer_from_session_or_learner_data(mock_request)
- assert actual_result is None
- mock_learner_data_from_db.assert_called_once_with(mock_request.user)
-
- @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db')
- @override_settings(ENTERPRISE_LEARNER_PORTAL_BASE_URL='http://localhost')
- def test_enterprise_customer_from_session_or_db_cache_miss_customer_exists(self, mock_learner_data_from_db):
- """
- When no customer data exists in the request session but a
- customer is associated with the requesting user, then ``enterprise_customer_from_session_or_learner_data()``
- should return the customer metadata.
- """
- mock_request = mock.Mock(session={})
- mock_enterprise_customer = {
- 'uuid': 'some-uuid',
- 'name': 'Best Corp',
- 'enable_learner_portal': True,
- 'slug': 'best-corp',
- }
- mock_learner_data_from_db.return_value = [
- {
- 'enterprise_customer': mock_enterprise_customer,
- },
- ]
-
- actual_result = enterprise_customer_from_session_or_learner_data(mock_request)
- assert actual_result['uuid'] == mock_enterprise_customer['uuid']
- mock_learner_data_from_db.assert_called_once_with(mock_request.user)
- # assert we cached the enterprise customer data in the request session after fetching it
- assert mock_request.session.get(ENTERPRISE_CUSTOMER_KEY_NAME) == mock_enterprise_customer
-
- @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db')
- def test_enterprise_customer_from_session_or_db_cache_hit_no_customer(self, mock_learner_data_from_db):
- """
- When customer data exists in the request session but it's null/empty,
- then ``enterprise_customer_from_session_or_learner_data()`` should return None.
- """
- mock_request = mock.Mock(session={
- ENTERPRISE_CUSTOMER_KEY_NAME: None,
- })
-
- actual_result = enterprise_customer_from_session_or_learner_data(mock_request)
- assert actual_result is None
- assert not mock_learner_data_from_db.called
-
- @ddt.data(True, False)
- @override_settings(ENTERPRISE_LEARNER_PORTAL_BASE_URL='http://localhost')
- def test_enterprise_learner_portal_message_customer_exists(self, enable_learner_portal):
- """
- When an enterprise customer exists with learner portal enabled, then
- ``get_enterprise_learner_portal_enabled_message()`` should return an appropriate message
- for that customer.
- """
- mock_enterprise_customer = {
- 'uuid': 'some-uuid',
- 'name': 'Best Corp',
- 'enable_learner_portal': enable_learner_portal,
- 'slug': 'best-corp',
- }
-
- actual_result = get_enterprise_learner_portal_enabled_message(mock_enterprise_customer)
- if not enable_learner_portal:
- assert actual_result is None
- else:
- assert 'To access the courses available to you through' in actual_result
- assert 'Best Corp' in actual_result
-
- def test_enterprise_learner_portal_message_no_customer(self):
- """
- When an enterprise customer does not exists, then
- ``get_enterprise_learner_portal_enabled_message()`` should return None.
- """
- actual_result = get_enterprise_learner_portal_enabled_message(None)
- assert actual_result is None
-
- @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None)
- def test_customer_uuid_for_request_sso_provider_id_customer_exists(self, mock_partial_pipeline):
- mock_idp = EnterpriseCustomerIdentityProviderFactory.create()
- mock_customer = mock_idp.enterprise_customer
- mock_request = mock.Mock(
- GET={'tpa_hint': mock_idp.provider_id},
- COOKIES={},
- session={},
- )
-
- actual_uuid = enterprise_customer_uuid_for_request(mock_request)
-
- expected_uuid = mock_customer.uuid
- assert expected_uuid == actual_uuid
- mock_partial_pipeline.assert_called_once_with(mock_request)
- assert ENTERPRISE_CUSTOMER_KEY_NAME not in mock_request.session
-
- @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db')
- @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None)
- def test_customer_uuid_for_request_sso_provider_id_customer_non_existent_but_exist_in_db(
- self,
- mock_partial_pipeline,
- mock_data_from_db,
- ):
- enterprise_customer_uuid = 'adab9a14-f263-42e6-a234-db707026c4a6'
- mock_request = mock.Mock(
- GET={'tpa_hint': 'my-third-party-auth'},
- COOKIES={},
- session={},
- )
- mock_data_from_db.return_value = [
- {'enterprise_customer': {'uuid': enterprise_customer_uuid}},
- ]
-
- actual_uuid = enterprise_customer_uuid_for_request(mock_request)
-
- assert actual_uuid == enterprise_customer_uuid
- mock_partial_pipeline.assert_called_once_with(mock_request)
- assert ENTERPRISE_CUSTOMER_KEY_NAME in mock_request.session
-
- @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None)
- def test_enterprise_uuid_for_request_from_query_params(self, mock_partial_pipeline):
- expected_uuid = 'my-uuid'
- mock_request = mock.Mock(
- GET={ENTERPRISE_CUSTOMER_KEY_NAME: expected_uuid},
- COOKIES={},
- session={},
- )
-
- actual_uuid = enterprise_customer_uuid_for_request(mock_request)
-
- assert expected_uuid == actual_uuid
- mock_partial_pipeline.assert_called_once_with(mock_request)
- assert ENTERPRISE_CUSTOMER_KEY_NAME not in mock_request.session
-
- @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None)
- def test_enterprise_uuid_for_request_from_cookies(self, mock_partial_pipeline):
- expected_uuid = 'my-uuid'
- mock_request = mock.Mock(
- GET={},
- COOKIES={settings.ENTERPRISE_CUSTOMER_COOKIE_NAME: expected_uuid},
- session={},
- )
-
- actual_uuid = enterprise_customer_uuid_for_request(mock_request)
-
- assert expected_uuid == actual_uuid
- mock_partial_pipeline.assert_called_once_with(mock_request)
- assert ENTERPRISE_CUSTOMER_KEY_NAME not in mock_request.session
-
- @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None)
- def test_enterprise_uuid_for_request_from_session(self, mock_partial_pipeline):
- expected_uuid = 'my-uuid'
- mock_request = mock.Mock(
- GET={},
- COOKIES={},
- session={ENTERPRISE_CUSTOMER_KEY_NAME: {'uuid': expected_uuid}},
- )
-
- actual_uuid = enterprise_customer_uuid_for_request(mock_request)
-
- assert expected_uuid == actual_uuid
- mock_partial_pipeline.assert_called_once_with(mock_request)
- assert {'uuid': expected_uuid} == mock_request.session.get(ENTERPRISE_CUSTOMER_KEY_NAME)
-
- @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db')
- @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None)
- def test_enterprise_uuid_for_request_cache_miss_but_exists_in_db(self, mock_partial_pipeline, mock_data_from_db):
- mock_request = mock.Mock(
- GET={},
- COOKIES={},
- session={},
- )
- mock_data_from_db.return_value = [
- {'enterprise_customer': {'uuid': 'my-uuid'}},
- ]
-
- actual_uuid = enterprise_customer_uuid_for_request(mock_request)
-
- expected_uuid = 'my-uuid'
- assert expected_uuid == actual_uuid
- mock_partial_pipeline.assert_called_once_with(mock_request)
- mock_data_from_db.assert_called_once_with(mock_request.user)
- assert {'uuid': 'my-uuid'} == mock_request.session[ENTERPRISE_CUSTOMER_KEY_NAME]
-
- @ddt.data(True, False)
- @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db', return_value=None)
- @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None)
- def test_enterprise_uuid_for_request_cache_miss_non_existent(
- self,
- is_user_authenticated,
- mock_partial_pipeline,
- mock_data_from_db
- ):
- mock_request = mock.Mock(
- GET={},
- COOKIES={},
- session={},
- )
- mock_request.user.is_authenticated = is_user_authenticated
-
- actual_uuid = enterprise_customer_uuid_for_request(mock_request)
-
- assert actual_uuid is None
- mock_partial_pipeline.assert_called_once_with(mock_request)
-
- if is_user_authenticated:
- mock_data_from_db.assert_called_once_with(mock_request.user)
- assert mock_request.session[ENTERPRISE_CUSTOMER_KEY_NAME] is None
- else:
- assert not mock_data_from_db.called
- assert ENTERPRISE_CUSTOMER_KEY_NAME not in mock_request.session
-
- def test_enterprise_customer_from_session(self):
- mock_request = mock.Mock(
- GET={},
- COOKIES={},
- session={},
- )
- mock_request.user.is_authenticated = True
-
- enterprise_customer = {
- 'name': 'abc',
- 'uuid': 'cf246b88-d5f6-4908-a522-fc307e0b0c59'
- }
-
- # set enterprise customer info with authenticate user
- add_enterprise_customer_to_session(mock_request, enterprise_customer)
- assert mock_request.session[ENTERPRISE_CUSTOMER_KEY_NAME] == enterprise_customer
-
- # Now try to set info with un-authenticated user
- mock_request.user.is_authenticated = False
- add_enterprise_customer_to_session(mock_request, None)
- # verify that existing session value should not be updated for un-authenticate user
- assert mock_request.session[ENTERPRISE_CUSTOMER_KEY_NAME] == enterprise_customer
-
- @ddt.data(None, object())
- def test_enterprise_customer_from_session_no_session_CACHE_MISS(self, request):
- assert enterprise_customer_from_session(request) == _CACHE_MISS
-
- def test_get_consent_notification_data_no_overrides(self):
- enterprise_customer = {
- 'name': 'abc',
- 'uuid': 'cf246b88-d5f6-4908-a522-fc307e0b0c59'
- }
-
- title_template, message_template = get_consent_notification_data(enterprise_customer)
-
- assert title_template is None
- assert message_template is None
-
- @mock.patch('openedx.features.enterprise_support.api.DataSharingConsentTextOverrides')
- def test_get_consent_notification_data(self, mock_override_model):
- enterprise_customer = {
- 'name': 'abc',
- 'uuid': 'cf246b88-d5f6-4908-a522-fc307e0b0c59'
- }
- mock_override = mock.Mock(
- declined_notification_title='the title',
- declined_notification_message='the message',
- )
- mock_override_model.objects.get.return_value = mock_override
-
- title_template, message_template = get_consent_notification_data(enterprise_customer)
-
- assert mock_override.declined_notification_title == title_template
- assert mock_override.declined_notification_message == message_template
diff --git a/openedx/features/enterprise_support/tests/test_context.py b/openedx/features/enterprise_support/tests/test_context.py
deleted file mode 100644
index 7004dd80892f..000000000000
--- a/openedx/features/enterprise_support/tests/test_context.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""
-Test the enterprise support APIs.
-"""
-from django.conf import settings
-from django.test.utils import override_settings
-
-from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
-from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
-from openedx.features.enterprise_support.context import get_enterprise_event_context
-from openedx.features.enterprise_support.tests.factories import (
- EnterpriseCourseEnrollmentFactory,
- EnterpriseCustomerUserFactory,
-)
-from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin
-
-
-@override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
-@skip_unless_lms
-class TestEnterpriseContext(EnterpriseServiceMockMixin, CacheIsolationTestCase):
- """
- Test enterprise event context APIs.
- """
- ENABLED_CACHES = ['default']
-
- @classmethod
- def setUpTestData(cls):
- cls.user = UserFactory.create(
- username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME,
- email='ent_worker@example.com',
- password='password123',
- )
- super().setUpTestData()
-
- def test_get_enterprise_event_context(self):
- course_enrollment = CourseEnrollmentFactory(user=self.user)
- course = course_enrollment.course
- enterprise_customer_user = EnterpriseCustomerUserFactory(user_id=self.user.id)
- EnterpriseCourseEnrollmentFactory(
- enterprise_customer_user=enterprise_customer_user,
- course_id=course.id
- )
- assert get_enterprise_event_context(course_id=course.id, user_id=self.user.id) == \
- {'enterprise_uuid': str(enterprise_customer_user.enterprise_customer_id)}
diff --git a/openedx/features/enterprise_support/tests/test_logout.py b/openedx/features/enterprise_support/tests/test_logout.py
deleted file mode 100644
index 2b976d07dbf1..000000000000
--- a/openedx/features/enterprise_support/tests/test_logout.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""
-Tests for logout for enterprise flow
-"""
-
-
-from unittest import mock
-
-import ddt
-from django.test.utils import override_settings
-from django.urls import reverse
-
-from common.djangoapps.student.tests.factories import UserFactory
-from common.djangoapps.util.testing import UrlResetMixin
-from common.test.utils import assert_dict_contains_subset
-from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
-from openedx.features.enterprise_support.api import enterprise_enabled
-from openedx.features.enterprise_support.tests import (
- FAKE_ENTERPRISE_CUSTOMER,
- factories,
-)
-from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin
-
-
-@ddt.ddt
-@override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
-@skip_unless_lms
-class EnterpriseLogoutTests(EnterpriseServiceMockMixin, CacheIsolationTestCase, UrlResetMixin):
- """ Tests for the enterprise logout functionality. """
-
- def setUp(self):
- super().setUp()
- self.user = UserFactory()
-
- self.enterprise_customer = FAKE_ENTERPRISE_CUSTOMER
- self.enterprise_learner = factories.EnterpriseCustomerUserFactory(user_id=self.user.id)
-
- self.client.login(username=self.user.username, password='test')
- patcher = mock.patch('openedx.features.enterprise_support.api.enterprise_customer_from_api')
- self.mock_enterprise_customer_from_api = patcher.start()
- self.mock_enterprise_customer_from_api.return_value = self.enterprise_customer
- self.addCleanup(patcher.stop)
-
- @ddt.data(
- ('https%3A%2F%2Ftest.edx.org%2Fcourses', False),
- ('/courses/course-v1:ARTS+D1+2018_T/course/', False),
- ('invalid-url', False),
- ('/enterprise/c5dad9a7-741c-4841-868f-850aca3ff848/course/Microsoft+DAT206x/enroll/', True),
- ('%2Fenterprise%2Fc5dad9a7-741c-4841-868f-850aca3ff848%2Fcourse%2FMicrosoft%2BDAT206x%2Fenroll%2F', True),
- ('/enterprise/handle_consent_enrollment/efd91463-dc40-4882-aeb9-38202131e7b2/course', True),
- ('%2Fenterprise%2Fhandle_consent_enrollment%2Fefd91463-dc40-4882-aeb9-38202131e7b2%2Fcourse', True),
- )
- @ddt.unpack
- def test_logout_enterprise_target(self, redirect_url, enterprise_target):
- url = '{logout_path}?redirect_url={redirect_url}'.format(
- logout_path=reverse('logout'),
- redirect_url=redirect_url
- )
- assert enterprise_enabled()
- response = self.client.get(url, HTTP_HOST='testserver')
- expected = {
- 'enterprise_target': enterprise_target,
- }
- assert_dict_contains_subset(self, expected, response.context_data)
-
- if enterprise_target:
- self.assertContains(response, 'We are signing you in.')
diff --git a/openedx/features/enterprise_support/tests/test_serializers.py b/openedx/features/enterprise_support/tests/test_serializers.py
deleted file mode 100644
index eff9993f8b59..000000000000
--- a/openedx/features/enterprise_support/tests/test_serializers.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""
-Tests for custom enterprise_support Serializers.
-"""
-from uuid import uuid4
-
-from django.test import TestCase
-from enterprise.models import LicensedEnterpriseCourseEnrollment
-
-from openedx.features.enterprise_support.serializers import EnterpriseCourseEnrollmentSerializer
-from openedx.features.enterprise_support.tests.factories import (
- EnterpriseCourseEnrollmentFactory,
- EnterpriseCustomerUserFactory,
-)
-
-
-class EnterpriseCourseEnrollmentSerializerTests(TestCase):
- """
- Tests for EnterpriseCourseEnrollmentSerializer.
- """
-
- @classmethod
- def setUpTestData(cls): # pylint: disable=super-method-not-called
- enterprise_customer_user = EnterpriseCustomerUserFactory()
- enterprise_course_enrollment = EnterpriseCourseEnrollmentFactory(
- enterprise_customer_user=enterprise_customer_user
- )
- cls.enterprise_customer_user = enterprise_customer_user
- cls.enterprise_course_enrollment = enterprise_course_enrollment
-
- def test_data_with_license(self):
- """ Verify the correct fields are serialized when the enrollment is licensed. """
-
- license_uuid = uuid4()
- licensed_ece = LicensedEnterpriseCourseEnrollment(
- license_uuid=license_uuid,
- enterprise_course_enrollment=self.enterprise_course_enrollment
- )
- licensed_ece.save()
-
- serializer = EnterpriseCourseEnrollmentSerializer(self.enterprise_course_enrollment)
-
- expected = {
- 'enterprise_customer_name': self.enterprise_customer_user.enterprise_customer.name,
- 'enterprise_customer_user_id': self.enterprise_customer_user.id,
- 'course_id': self.enterprise_course_enrollment.course_id,
- 'saved_for_later': self.enterprise_course_enrollment.saved_for_later,
- 'license': {
- 'uuid': str(license_uuid),
- 'is_revoked': licensed_ece.is_revoked,
- }
- }
- self.assertDictEqual(serializer.data, expected) # noqa: PT009
-
- def test_data_without_license(self):
- """ Verify the correct fields are serialized when the enrollment is not licensed. """
-
- serializer = EnterpriseCourseEnrollmentSerializer(self.enterprise_course_enrollment)
-
- expected = {
- 'enterprise_customer_name': self.enterprise_customer_user.enterprise_customer.name,
- 'enterprise_customer_user_id': self.enterprise_customer_user.id,
- 'course_id': self.enterprise_course_enrollment.course_id,
- 'saved_for_later': self.enterprise_course_enrollment.saved_for_later,
- 'license': None
- }
- self.assertDictEqual(serializer.data, expected) # noqa: PT009
diff --git a/openedx/features/enterprise_support/tests/test_signals.py b/openedx/features/enterprise_support/tests/test_signals.py
deleted file mode 100644
index 005fefbd1000..000000000000
--- a/openedx/features/enterprise_support/tests/test_signals.py
+++ /dev/null
@@ -1,252 +0,0 @@
-"""Tests of email marketing signal handlers."""
-
-
-import logging
-from datetime import timedelta
-from unittest.mock import patch
-
-import ddt
-from django.test.utils import override_settings
-from django.utils.timezone import now
-from edx_django_utils.cache import TieredCache
-from opaque_keys.edx.keys import CourseKey
-from slumber.exceptions import HttpClientError, HttpServerError
-
-# from requests.exceptions import HTTPError
-from testfixtures import LogCapture
-
-from common.djangoapps.course_modes.tests.factories import CourseModeFactory
-from common.djangoapps.student.models import CourseEnrollmentAttribute
-from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
-from lms.djangoapps.certificates.signals import listen_for_passing_grade
-from openedx.core.djangoapps.commerce.utils import ECOMMERCE_DATE_FORMAT
-from openedx.core.djangoapps.credit.tests.test_api import TEST_ECOMMERCE_WORKER
-from openedx.core.djangoapps.signals.signals import COURSE_ASSESSMENT_GRADE_CHANGED, COURSE_GRADE_NOW_PASSED
-from openedx.features.enterprise_support.tests.factories import (
- EnterpriseCourseEnrollmentFactory,
- EnterpriseCustomerFactory,
- EnterpriseCustomerUserFactory,
-)
-from openedx.features.enterprise_support.utils import get_data_consent_share_cache_key
-from xmodule.modulestore.tests.django_utils import (
- SharedModuleStoreTestCase, # pylint: disable=wrong-import-order
-)
-from xmodule.modulestore.tests.factories import CourseFactory # pylint: disable=wrong-import-order
-
-log = logging.getLogger(__name__)
-
-LOGGER_NAME = "openedx.features.enterprise_support.signals"
-
-TEST_EMAIL = "test@edx.org"
-
-
-@ddt.ddt
-@override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
-@override_settings(ECOMMERCE_SERVICE_WORKER_USERNAME=TEST_ECOMMERCE_WORKER)
-class EnterpriseSupportSignals(SharedModuleStoreTestCase):
- """
- Tests for the enterprise support signals.
- """
- def setUp(self):
- UserFactory.create(username=TEST_ECOMMERCE_WORKER)
- self.user = UserFactory.create(username='test', email=TEST_EMAIL)
- self.course_id = 'course-v1:edX+DemoX+Demo_Course'
- self.enterprise_customer = EnterpriseCustomerFactory()
- self.enterprise_customer_uuid = str(self.enterprise_customer.uuid)
- super().setUp()
-
- @staticmethod
- def _create_dsc_cache(user_id, course_id, enterprise_customer_uuid):
- consent_cache_key = get_data_consent_share_cache_key(user_id, course_id, enterprise_customer_uuid)
- TieredCache.set_all_tiers(consent_cache_key, 0)
-
- @staticmethod
- def _is_dsc_cache_found(user_id, course_id, enterprise_customer_uuid):
- consent_cache_key = get_data_consent_share_cache_key(user_id, course_id, enterprise_customer_uuid)
- data_sharing_consent_needed_cache = TieredCache.get_cached_response(consent_cache_key)
- return data_sharing_consent_needed_cache.is_found
-
- def _create_enterprise_enrollment(self, user_id, course_id):
- """
- Create enterprise user and enrollment
- """
- enterprise_customer_user = EnterpriseCustomerUserFactory(
- user_id=user_id,
- enterprise_customer=self.enterprise_customer
- )
- EnterpriseCourseEnrollmentFactory(
- course_id=course_id,
- enterprise_customer_user=enterprise_customer_user,
- )
-
- def test_signal_update_dsc_cache_on_course_enrollment(self):
- """
- make sure update_dsc_cache_on_course_enrollment signal clears cache when Enterprise Course Enrollment
- takes place
- """
-
- self._create_dsc_cache(self.user.id, self.course_id, self.enterprise_customer_uuid)
- assert self._is_dsc_cache_found(self.user.id, self.course_id, self.enterprise_customer_uuid)
-
- self._create_enterprise_enrollment(self.user.id, self.course_id)
- assert not self._is_dsc_cache_found(self.user.id, self.course_id, self.enterprise_customer_uuid)
-
- def test_signal_update_dsc_cache_on_enterprise_customer_update(self):
- """
- make sure update_dsc_cache_on_enterprise_customer_update signal clears data_sharing_consent_needed cache after
- enable_data_sharing_consent flag is changed.
- """
-
- self._create_enterprise_enrollment(self.user.id, self.course_id)
- self._create_dsc_cache(self.user.id, self.course_id, self.enterprise_customer_uuid)
- assert self._is_dsc_cache_found(self.user.id, self.course_id, self.enterprise_customer_uuid)
-
- # updating enable_data_sharing_consent flag
- self.enterprise_customer.enable_data_sharing_consent = False
- self.enterprise_customer.save()
-
- assert not self._is_dsc_cache_found(self.user.id, self.course_id, self.enterprise_customer_uuid)
-
- def _create_enrollment_to_refund(self, no_of_days_placed=10, enterprise_enrollment_exists=True):
- """Create enrollment to refund. """
- date_placed = now() - timedelta(days=no_of_days_placed)
- course = CourseFactory.create(display_name='test course', run="Testing_course", start=date_placed)
- enrollment = CourseEnrollmentFactory(
- course_id=course.id,
- user=self.user,
- mode="verified",
- )
- CourseModeFactory.create(course_id=course.id, mode_slug='verified')
- CourseEnrollmentAttribute.objects.create(
- enrollment=enrollment,
- name='date_placed',
- namespace='order',
- value=date_placed.strftime(ECOMMERCE_DATE_FORMAT)
- )
- CourseEnrollmentAttribute.objects.create(
- enrollment=enrollment,
- name='order_number',
- namespace='order',
- value='EDX-000000001'
- )
-
- if enterprise_enrollment_exists:
- self._create_enterprise_enrollment(self.user.id, course.id)
-
- return enrollment
-
- @patch('common.djangoapps.student.models.course_enrollment.CourseEnrollment.is_order_voucher_refundable')
- @ddt.data(
- (True, True, 2, True, False), # test if skip_refund
- (False, True, 20, True, False), # test refundable time passed
- (False, False, 2, True, False), # test not enterprise enrollment
- (False, True, 2, False, False), # test order voucher expiration date has already passed
- (False, True, 2, True, True), # success: no skip_refund, is enterprise enrollment, coupon voucher is refundable
- # and is still in refundable window.
- )
- @ddt.unpack
- def test_refund_order_voucher(
- self,
- skip_refund,
- enterprise_enrollment_exists,
- no_of_days_placed,
- order_voucher_refundable,
- api_called,
- mock_is_order_voucher_refundable
- ):
- """
- Test refund_order_voucher signal
- """
- mock_is_order_voucher_refundable.return_value = order_voucher_refundable
- enrollment = self._create_enrollment_to_refund(no_of_days_placed, enterprise_enrollment_exists)
- with patch('openedx.features.enterprise_support.signals.ecommerce_api_client') as mock_ecommerce_api_client:
- enrollment.update_enrollment(is_active=False, skip_refund=skip_refund)
- assert mock_ecommerce_api_client.called == api_called
-
- @patch('common.djangoapps.student.models.course_enrollment.CourseEnrollment.is_order_voucher_refundable')
- @ddt.data(
- (HttpClientError, 'INFO'),
- (HttpServerError, 'ERROR'),
- (Exception, 'ERROR'),
- )
- @ddt.unpack
- def test_refund_order_voucher_with_client_errors(self, mock_error, log_level, mock_is_order_voucher_refundable):
- """
- Test refund_order_voucher signal client_error.
- """
- mock_is_order_voucher_refundable.return_value = True
- enrollment = self._create_enrollment_to_refund()
- with patch('openedx.features.enterprise_support.signals.ecommerce_api_client') as mock_ecommerce_api_client:
- client_instance = mock_ecommerce_api_client.return_value
- client_instance.enterprise.coupons.create_refunded_voucher.post.side_effect = mock_error()
- with LogCapture(LOGGER_NAME) as logger:
- enrollment.update_enrollment(is_active=False)
- assert mock_ecommerce_api_client.called is True
- logger.check(
- (
- LOGGER_NAME,
- log_level,
- 'Encountered {} from ecommerce while creating refund voucher. ' # noqa: UP032
- 'Order=EDX-000000001, enrollment={}, user={}'.format(
- mock_error.__name__, enrollment, enrollment.user
- ),
- )
- )
-
- def test_handle_enterprise_learner_passing_grade(self):
- """
- Test to assert transmit_single_learner_data is called when COURSE_GRADE_NOW_PASSED signal is fired
- """
- with patch(
- 'integrated_channels.integrated_channel.tasks.transmit_single_learner_data.apply_async',
- return_value=None
- ) as mock_task_apply:
- course_key = CourseKey.from_string(self.course_id)
- COURSE_GRADE_NOW_PASSED.disconnect(dispatch_uid='new_passing_learner')
- COURSE_GRADE_NOW_PASSED.send(sender=None, user=self.user, course_id=course_key)
- assert not mock_task_apply.called
-
- self._create_enterprise_enrollment(self.user.id, self.course_id)
- task_kwargs = {
- 'username': self.user.username,
- 'course_run_id': self.course_id
- }
- COURSE_GRADE_NOW_PASSED.send(sender=None, user=self.user, course_id=course_key)
- mock_task_apply.assert_called_once_with(kwargs=task_kwargs)
- COURSE_GRADE_NOW_PASSED.connect(listen_for_passing_grade, dispatch_uid='new_passing_learner')
-
- def test_handle_enterprise_learner_subsection(self):
- """
- Test to assert transmit_subsection_learner_data is called when COURSE_ASSESSMENT_GRADE_CHANGED signal is fired.
- """
- with patch(
- 'integrated_channels.integrated_channel.tasks.transmit_single_subsection_learner_data.apply_async',
- return_value=None
- ) as mock_task_apply:
- course_key = CourseKey.from_string(self.course_id)
- COURSE_ASSESSMENT_GRADE_CHANGED.disconnect()
- COURSE_ASSESSMENT_GRADE_CHANGED.send(
- sender=None,
- user=self.user,
- course_id=course_key,
- subsection_id='subsection_id',
- subsection_grade=1.0
- )
- assert not mock_task_apply.called
-
- self._create_enterprise_enrollment(self.user.id, self.course_id)
- task_kwargs = {
- 'username': self.user.username,
- 'course_run_id': self.course_id,
- 'subsection_id': 'subsection_id',
- 'grade': '1.0'
- }
- COURSE_ASSESSMENT_GRADE_CHANGED.send(
- sender=None,
- user=self.user,
- course_id=course_key,
- subsection_id='subsection_id',
- subsection_grade=1.0
- )
- mock_task_apply.assert_called_once_with(kwargs=task_kwargs)
- COURSE_ASSESSMENT_GRADE_CHANGED.connect(listen_for_passing_grade)
diff --git a/openedx/features/enterprise_support/tests/test_utils.py b/openedx/features/enterprise_support/tests/test_utils.py
deleted file mode 100644
index d394ab2967ba..000000000000
--- a/openedx/features/enterprise_support/tests/test_utils.py
+++ /dev/null
@@ -1,686 +0,0 @@
-"""
-Test the enterprise support utils.
-"""
-
-import json
-import uuid
-from unittest import mock
-
-import ddt
-from completion.models import BlockCompletion
-from completion.test_utils import CompletionWaffleTestMixin
-from completion.waffle import ENABLE_COMPLETION_TRACKING_SWITCH
-from django.conf import settings
-from django.contrib.sites.models import Site
-from django.test import TestCase
-from django.test.utils import override_settings
-from django.urls import NoReverseMatch
-from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch
-from opaque_keys.edx.keys import CourseKey, UsageKey
-
-from common.djangoapps.student.models import CourseEnrollment
-from common.djangoapps.student.tests.factories import UserFactory
-from openedx.core.djangolib.testing.utils import skip_unless_lms
-from openedx.features.enterprise_support.tests.factories import (
- EnterpriseCustomerBrandingConfigurationFactory,
- EnterpriseCustomerFactory,
- EnterpriseCustomerUserFactory,
-)
-from openedx.features.enterprise_support.utils import (
- ENTERPRISE_HEADER_LINKS,
- _user_has_social_auth_record,
- clear_data_consent_share_cache,
- enterprise_fields_only,
- fetch_enterprise_customer_by_id,
- get_data_consent_share_cache_key,
- get_enterprise_learner_generic_name,
- get_enterprise_learner_portal,
- get_enterprise_readonly_account_fields,
- get_enterprise_sidebar_context,
- get_enterprise_slug_login_url,
- get_provider_login_url,
- handle_enterprise_cookies_for_logistration,
- is_course_accessed,
- is_enterprise_learner,
- update_account_settings_context_for_enterprise,
- update_logistration_context_for_enterprise,
- update_third_party_auth_context_for_enterprise,
-)
-from xmodule.modulestore.tests.django_utils import (
- SharedModuleStoreTestCase, # pylint: disable=wrong-import-order
-)
-from xmodule.modulestore.tests.factories import ( # pylint: disable=wrong-import-order
- BlockFactory,
- CourseFactory,
-)
-
-TEST_PASSWORD = 'test'
-
-
-@ddt.ddt
-@override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
-@skip_unless_lms
-class TestEnterpriseUtils(TestCase):
- """
- Test enterprise support utils.
- """
-
- @classmethod
- def setUpTestData(cls):
- cls.user = UserFactory.create(password='password')
- super().setUpTestData()
-
- @mock.patch('openedx.features.enterprise_support.utils.get_cache_key')
- def test_get_data_consent_share_cache_key(self, mock_get_cache_key):
- expected_cache_key = mock_get_cache_key.return_value
-
- assert expected_cache_key == get_data_consent_share_cache_key(
- 'some-user-id',
- 'some-course-id',
- '1a9cae8f-abb7-4336-b075-6ff32ecf73de'
- )
-
- mock_get_cache_key.assert_called_once_with(
- type='data_sharing_consent_needed',
- user_id='some-user-id',
- course_id='some-course-id',
- enterprise_customer_uuid='1a9cae8f-abb7-4336-b075-6ff32ecf73de'
- )
-
- @mock.patch('openedx.features.enterprise_support.utils.get_cache_key')
- @mock.patch('openedx.features.enterprise_support.utils.TieredCache')
- def test_clear_data_consent_share_cache(self, mock_tiered_cache, mock_get_cache_key):
- user_id = 'some-user-id'
- course_id = 'some-course-id'
- enterprise_customer_uuid = '1a9cae8f-abb7-4336-b075-6ff32ecf73de'
-
- clear_data_consent_share_cache(user_id, course_id, enterprise_customer_uuid)
-
- mock_get_cache_key.assert_called_once_with(
- type='data_sharing_consent_needed',
- user_id='some-user-id',
- course_id='some-course-id',
- enterprise_customer_uuid=enterprise_customer_uuid
- )
- mock_tiered_cache.delete_all_tiers.assert_called_once_with(mock_get_cache_key.return_value)
-
- @mock.patch('openedx.features.enterprise_support.utils.update_third_party_auth_context_for_enterprise')
- def test_update_logistration_context_no_customer_data(self, mock_update_tpa_context):
- request = mock.Mock()
- context = {}
- enterprise_customer = {}
-
- update_logistration_context_for_enterprise(request, context, enterprise_customer)
-
- assert context['enable_enterprise_sidebar'] is False
- mock_update_tpa_context.assert_called_once_with(request, context, enterprise_customer)
-
- @mock.patch('openedx.features.enterprise_support.utils.update_third_party_auth_context_for_enterprise')
- @mock.patch('openedx.features.enterprise_support.utils.get_enterprise_sidebar_context', return_value={})
- def test_update_logistration_context_no_sidebar_context(self, mock_sidebar_context, mock_update_tpa_context):
- request = mock.Mock(GET={'proxy_login': False})
- context = {}
- enterprise_customer = {'key': 'value'}
-
- update_logistration_context_for_enterprise(request, context, enterprise_customer)
-
- assert context['enable_enterprise_sidebar'] is False
- mock_update_tpa_context.assert_called_once_with(request, context, enterprise_customer)
- mock_sidebar_context.assert_called_once_with(enterprise_customer, False)
-
- @mock.patch('openedx.features.enterprise_support.utils.update_third_party_auth_context_for_enterprise')
- @mock.patch('openedx.features.enterprise_support.utils.get_enterprise_sidebar_context')
- @mock.patch('openedx.features.enterprise_support.utils.enterprise_fields_only')
- def test_update_logistration_context_with_sidebar_context(
- self, mock_enterprise_fields_only, mock_sidebar_context, mock_update_tpa_context
- ):
- request = mock.Mock(GET={'proxy_login': False})
- context = {
- 'data': {
- 'registration_form_desc': {
- 'thing-1': 'one',
- 'thing-2': 'two',
- },
- },
- }
- enterprise_customer = {'name': 'pied-piper'}
- mock_sidebar_context.return_value = {
- 'sidebar-1': 'one',
- 'sidebar-2': 'two',
- }
-
- update_logistration_context_for_enterprise(request, context, enterprise_customer)
-
- assert context['enable_enterprise_sidebar'] is True
- mock_update_tpa_context.assert_called_once_with(request, context, enterprise_customer)
- mock_enterprise_fields_only.assert_called_once_with(context['data']['registration_form_desc'])
- mock_sidebar_context.assert_called_once_with(enterprise_customer, False)
-
- @ddt.data(
- {'is_proxy_login': True, 'branding_configuration': {'logo': 'path-to-logo'}},
- {'is_proxy_login': True, 'branding_configuration': {}},
- {'is_proxy_login': False, 'branding_configuration': {'nonsense': 'foo'}},
- )
- @ddt.unpack
- def test_get_enterprise_sidebar_context(self, is_proxy_login, branding_configuration):
- enterprise_customer = {
- 'name': 'pied-piper',
- 'branding_configuration': branding_configuration,
- }
- actual_result = get_enterprise_sidebar_context(enterprise_customer, is_proxy_login)
-
- assert 'pied-piper' == actual_result['enterprise_name']
- expected_logo_url = branding_configuration.get('logo', '')
- assert expected_logo_url == actual_result['enterprise_logo_url']
- assert 'pied-piper' in str(actual_result['enterprise_branded_welcome_string'])
-
- @ddt.data(
- ('notfoundpage', 0),
- )
- @ddt.unpack
- def test_enterprise_customer_for_request_called_on_404(self, resource, expected_calls):
- """
- Test enterprise customer API is not called from 404 page
- """
- self.client.login(username=self.user.username, password='password')
-
- with mock.patch(
- 'openedx.features.enterprise_support.api.enterprise_customer_for_request'
- ) as mock_customer_request:
- self.client.get(resource)
- assert mock_customer_request.call_count == expected_calls
-
- @mock.patch('openedx.features.enterprise_support.utils.configuration_helpers.get_value')
- def test_enterprise_fields_only(self, mock_get_value):
- mock_get_value.return_value = ['cat', 'dog', 'sheep']
- fields = {
- 'fields': [
- {'name': 'cat', 'value': 1},
- {'name': 'fish', 'value': 2},
- {'name': 'dog', 'value': 3},
- {'name': 'emu', 'value': 4},
- {'name': 'sheep', 'value': 5},
- ],
- }
-
- expected_fields = [
- {'name': 'fish', 'value': 2},
- {'name': 'emu', 'value': 4},
- ]
- assert expected_fields == enterprise_fields_only(fields)
-
- @mock.patch('openedx.features.enterprise_support.utils.third_party_auth')
- def test_update_third_party_auth_context_for_enterprise(self, mock_tpa):
- context = {
- 'data': {
- 'third_party_auth': {
- 'errorMessage': 'Widget error.',
- },
- },
- }
-
- enterprise_customer = mock.Mock()
- request = mock.Mock()
-
- # This will directly modify context
- update_third_party_auth_context_for_enterprise(request, context, enterprise_customer)
-
- assert 'We are sorry, you are not authorized' in str(context['data']['third_party_auth']['errorMessage'])
- assert 'Widget error.' in str(context['data']['third_party_auth']['errorMessage'])
- assert [] == context['data']['third_party_auth']['providers']
- assert [] == context['data']['third_party_auth']['secondaryProviders']
- assert not context['data']['third_party_auth']['autoSubmitRegForm']
- assert 'Just a couple steps' in str(context['data']['third_party_auth']['autoRegisterWelcomeMessage'])
- assert 'Continue' == str(context['data']['third_party_auth']['registerFormSubmitButtonText'])
- mock_tpa.pipeline.get.assert_called_once_with(request)
-
- @mock.patch('openedx.features.enterprise_support.utils.standard_cookie_settings', return_value={})
- def test_handle_enterprise_cookies_for_logistration(self, mock_cookie_settings):
- context = {'enable_enterprise_sidebar': True}
- request = mock.Mock()
- response = mock.Mock()
-
- handle_enterprise_cookies_for_logistration(request, response, context)
-
- response.set_cookie.assert_called_once_with(
- 'experiments_is_enterprise',
- 'true',
- )
- response.delete_cookie.assert_called_once_with(
- settings.ENTERPRISE_CUSTOMER_COOKIE_NAME,
- domain=settings.BASE_COOKIE_DOMAIN,
- )
- mock_cookie_settings.assert_called_once_with(request)
-
- @mock.patch('openedx.features.enterprise_support.utils.get_enterprise_readonly_account_fields', return_value=[])
- def test_update_account_settings_context_for_enterprise(self, mock_get_fields):
- enterprise_customer = {
- 'name': 'pied-piper',
- 'identity_provider': None,
- }
- context = {}
- user = mock.Mock()
-
- update_account_settings_context_for_enterprise(context, enterprise_customer, user)
-
- expected_context = {
- 'enterprise_name': 'pied-piper',
- 'sync_learner_profile_data': False,
- 'edx_support_url': settings.SUPPORT_SITE_LINK,
- 'enterprise_readonly_account_fields': {
- 'fields': mock_get_fields.return_value,
- },
- }
- mock_get_fields.assert_called_once_with(user)
- assert expected_context == context
-
- @ddt.data(settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS, ['username', 'email', 'country'])
- @mock.patch('openedx.features.enterprise_support.utils.get_current_request')
- @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_for_request')
- def test_get_enterprise_readonly_account_fields_no_sync_learner_profile_data(
- self, readonly_fields, mock_customer_for_request, mock_get_current_request,
- ):
- mock_get_current_request.return_value = mock.Mock(
- GET={'enterprise_customer': 'some-uuid'},
- )
- mock_customer_for_request.return_value = {
- 'uuid': 'some-uuid',
- 'identity_provider': None,
- 'identity_providers': [],
- }
- user = mock.Mock()
-
- with override_settings(ENTERPRISE_READONLY_ACCOUNT_FIELDS=readonly_fields):
- actual_fields = get_enterprise_readonly_account_fields(user)
- assert set() == actual_fields
- mock_customer_for_request.assert_called_once_with(mock_get_current_request.return_value)
- mock_get_current_request.assert_called_once_with()
-
- @mock.patch('openedx.features.enterprise_support.utils.UserSocialAuth')
- @mock.patch('openedx.features.enterprise_support.utils.get_current_request')
- @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_for_request')
- @mock.patch('openedx.features.enterprise_support.utils.third_party_auth')
- def test_get_enterprise_readonly_account_fields_with_idp_sync(
- self, mock_tpa, mock_customer_for_request, mock_get_current_request, mock_user_social_auth
- ):
- mock_get_current_request.return_value = mock.Mock(
- GET={'enterprise_customer': 'some-uuid'},
- )
- mock_customer_for_request.return_value = {
- 'uuid': 'some-uuid',
- 'identity_provider': 'mock-idp',
- 'identity_providers': [
- {
- "provider_id": "mock-idp",
- },
- ]
- }
- mock_idp = mock.MagicMock(
- backend_name='mock-backend',
- sync_learner_profile_data=True,
- )
- mock_tpa.provider.Registry.get.return_value = mock_idp
- user = mock.Mock()
-
- actual_fields = get_enterprise_readonly_account_fields(user)
-
- assert set(settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS) == actual_fields
-
- mock_customer_for_request.assert_called_once_with(mock_get_current_request.return_value)
- mock_get_current_request.assert_called_once_with()
-
- mock_tpa.provider.Registry.get.assert_called_with(provider_id='mock-idp')
- mock_select_related = mock_user_social_auth.objects.select_related
- mock_select_related.assert_called_once_with('user')
- mock_select_related.return_value.filter.assert_called_once_with(
- provider__in=[mock_idp.backend_name],
- user=user
- )
-
- @override_waffle_flag(ENTERPRISE_HEADER_LINKS, True)
- def test_get_enterprise_learner_portal_uncached(self):
- """
- Test that only an enabled enterprise portal is returned,
- and that it matches the customer UUID provided in the request.
- """
- enterprise_customer_user = EnterpriseCustomerUserFactory(active=True, user_id=self.user.id)
- EnterpriseCustomerBrandingConfigurationFactory(
- enterprise_customer=enterprise_customer_user.enterprise_customer,
- )
- enterprise_customer_user.enterprise_customer.enable_learner_portal = True
- enterprise_customer_user.enterprise_customer.save()
-
- request = mock.MagicMock(session={}, user=self.user)
- # Indicate the "preferred" customer in the request
- request.GET = {'enterprise_customer': enterprise_customer_user.enterprise_customer.uuid}
-
- # Create another enterprise customer association for the same user.
- # There should be no data returned for this customer's portal,
- # because we filter for only the enterprise customer uuid found in the request.
- other_enterprise_customer_user = EnterpriseCustomerUserFactory(active=True, user_id=self.user.id)
- other_enterprise_customer_user.enable_learner_portal = True
- other_enterprise_customer_user.save()
-
- portal = get_enterprise_learner_portal(request)
- self.assertDictEqual(portal, { # noqa: PT009
- 'name': enterprise_customer_user.enterprise_customer.name,
- 'slug': enterprise_customer_user.enterprise_customer.slug,
- 'logo': enterprise_customer_user.enterprise_customer.safe_branding_configuration.safe_logo_url,
- })
-
- @override_waffle_flag(ENTERPRISE_HEADER_LINKS, True)
- def test_get_enterprise_learner_portal_no_branding_config(self):
- """
- Test that only an enabled enterprise portal is returned,
- and that it matches the customer UUID provided in the request,
- even if no branding config is associated with the customer.
- """
- enterprise_customer_user = EnterpriseCustomerUserFactory.create(active=True, user_id=self.user.id)
- enterprise_customer_user.enterprise_customer.enable_learner_portal = True
- enterprise_customer_user.enterprise_customer.save()
-
- request = mock.MagicMock(session={}, user=self.user)
- # Indicate the "preferred" customer in the request
- request.GET = {'enterprise_customer': enterprise_customer_user.enterprise_customer.uuid}
-
- portal = get_enterprise_learner_portal(request)
- self.assertDictEqual(portal, { # noqa: PT009
- 'name': enterprise_customer_user.enterprise_customer.name,
- 'slug': enterprise_customer_user.enterprise_customer.slug,
- 'logo': enterprise_customer_user.enterprise_customer.safe_branding_configuration.safe_logo_url,
- })
-
- @override_waffle_flag(ENTERPRISE_HEADER_LINKS, True)
- def test_get_enterprise_learner_portal_no_customer_from_request(self):
- """
- Test that only one enabled enterprise portal is returned,
- even if enterprise_customer_uuid_from_request() returns None.
- """
- # Create another enterprise customer association for the same user.
- # There should be no data returned for this customer's portal,
- # because another customer is later created with a more recent active/modified time.
- other_enterprise_customer_user = EnterpriseCustomerUserFactory(active=True, user_id=self.user.id)
- other_enterprise_customer_user.enable_learner_portal = True
- other_enterprise_customer_user.save()
-
- enterprise_customer_user = EnterpriseCustomerUserFactory(active=True, user_id=self.user.id)
- EnterpriseCustomerBrandingConfigurationFactory(
- enterprise_customer=enterprise_customer_user.enterprise_customer,
- )
- enterprise_customer_user.enterprise_customer.enable_learner_portal = True
- enterprise_customer_user.enterprise_customer.save()
-
- request = mock.MagicMock(session={}, user=self.user)
-
- with mock.patch(
- 'openedx.features.enterprise_support.api.enterprise_customer_uuid_for_request',
- return_value=None,
- ):
- portal = get_enterprise_learner_portal(request)
-
- self.assertDictEqual(portal, { # noqa: PT009
- 'name': enterprise_customer_user.enterprise_customer.name,
- 'slug': enterprise_customer_user.enterprise_customer.slug,
- 'logo': enterprise_customer_user.enterprise_customer.safe_branding_configuration.safe_logo_url,
- })
-
- @override_waffle_flag(ENTERPRISE_HEADER_LINKS, True)
- def test_get_enterprise_learner_portal_cached(self):
- enterprise_customer_data = {
- 'name': 'Enabled Customer',
- 'slug': 'enabled_customer',
- 'logo': 'https://logo.url',
- }
- request = mock.MagicMock(session={
- 'enterprise_learner_portal': json.dumps(enterprise_customer_data)
- }, user=self.user)
- portal = get_enterprise_learner_portal(request)
- self.assertDictEqual(portal, enterprise_customer_data) # noqa: PT009
-
- @override_waffle_flag(ENTERPRISE_HEADER_LINKS, True)
- def test_get_enterprise_learner_portal_no_enterprise_user(self):
- request = mock.MagicMock(session={}, user=self.user)
- # Indicate the "preferred" customer in the request
- request.GET = {'enterprise_customer': uuid.uuid4()}
-
- portal = get_enterprise_learner_portal(request)
- assert portal is None
-
- def test_get_enterprise_learner_generic_name_404_pages(self):
- request = mock.Mock(view_name='404')
- assert get_enterprise_learner_generic_name(request) is None
-
- @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_for_request')
- def test_get_enterprise_learner_generic_name_with_replacement(self, mock_customer_for_request):
- request = mock.Mock()
- mock_customer_for_request.return_value = {
- 'name': 'Test Corp',
- 'replace_sensitive_sso_username': True,
- }
- generic_name = get_enterprise_learner_generic_name(request)
- assert 'Test CorpLearner' == generic_name
-
- @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_for_request')
- def test_get_enterprise_learner_generic_name_no_replacement(self, mock_customer_for_request):
- request = mock.Mock()
- mock_customer_for_request.return_value = {
- 'name': 'Test Corp',
- 'replace_sensitive_sso_username': False,
- }
- generic_name = get_enterprise_learner_generic_name(request)
- assert '' == generic_name
-
- def test_is_enterprise_learner(self):
- with mock.patch(
- 'django.core.cache.cache.set'
- ) as mock_cache_set:
- EnterpriseCustomerUserFactory.create(active=True, user_id=self.user.id)
- assert is_enterprise_learner(self.user)
- assert is_enterprise_learner(self.user.id)
-
- assert mock_cache_set.called
-
- def test_is_enterprise_learner_no_enterprise_user(self):
- with mock.patch(
- 'django.core.cache.cache.set'
- ) as mock_cache_set:
- assert not is_enterprise_learner(self.user)
-
- assert not mock_cache_set.called
-
- @mock.patch('django.core.cache.cache.set')
- @mock.patch('django.core.cache.cache.get')
- @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled', return_value=False)
- def test_is_enterprise_learner_enterprise_disabled(self, _, mock_cache_get, mock_cache_set): # noqa: PT019
- assert not is_enterprise_learner(self.user)
- assert not is_enterprise_learner(self.user.id)
- assert not mock_cache_get.called
- assert not mock_cache_set.called
-
- @mock.patch('openedx.features.enterprise_support.utils.reverse')
- def test_get_enterprise_slug_login_url_no_reverse_match(self, mock_reverse):
- mock_reverse.side_effect = NoReverseMatch
- assert get_enterprise_slug_login_url() is None
- mock_reverse.assert_called_once_with('enterprise_slug_login')
-
- @mock.patch('openedx.features.enterprise_support.utils.reverse')
- def test_get_enterprise_slug_login_url_with_match(self, mock_reverse):
- assert get_enterprise_slug_login_url() is not None
- mock_reverse.assert_called_once_with('enterprise_slug_login')
-
- def test_fetch_enterprise_customer_by_id(self):
- the_uuid = uuid.uuid4()
- customer = EnterpriseCustomerFactory.create(uuid=the_uuid)
- assert customer == fetch_enterprise_customer_by_id(the_uuid)
-
- @mock.patch('openedx.features.enterprise_support.utils.get_next_url_for_login_page')
- @mock.patch('openedx.features.enterprise_support.utils.third_party_auth')
- def test_get_provider_login_url_no_redirect_url(self, mock_tpa, mock_next_login_url):
- request = mock.Mock()
- provider_id = 'anything'
-
- login_url = get_provider_login_url(request, provider_id)
- assert mock_tpa.pipeline.get_login_url.return_value == login_url
- mock_tpa.pipeline.get_login_url.assert_called_once_with(
- provider_id,
- mock_tpa.pipeline.AUTH_ENTRY_LOGIN,
- redirect_url=mock_next_login_url.return_value,
- )
- mock_next_login_url.assert_called_once_with(request)
-
- @mock.patch('openedx.features.enterprise_support.utils.get_next_url_for_login_page')
- @mock.patch('openedx.features.enterprise_support.utils.third_party_auth')
- def test_get_provider_login_url_with_redirect_url(self, mock_tpa, mock_next_login_url):
- request = mock.Mock()
- provider_id = 'anything'
- redirect_url = 'the-next-url'
-
- login_url = get_provider_login_url(request, provider_id, redirect_url=redirect_url)
- assert mock_tpa.pipeline.get_login_url.return_value == login_url
- mock_tpa.pipeline.get_login_url.assert_called_once_with(
- provider_id,
- mock_tpa.pipeline.AUTH_ENTRY_LOGIN,
- redirect_url=redirect_url,
- )
- assert not mock_next_login_url.called
-
- @mock.patch('openedx.features.enterprise_support.utils.UserSocialAuth')
- @mock.patch('openedx.features.enterprise_support.utils.third_party_auth')
- def test_user_has_social_auth_record(self, mock_tpa, mock_user_social_auth):
- user = mock.Mock()
- enterprise_customer = {
- 'identity_providers': [
- {'provider_id': 'mock-idp'},
- ],
- }
- mock_idp = mock.MagicMock(backend_name='mock-backend')
- mock_tpa.provider.Registry.get.return_value = mock_idp
- mock_user_social_auth.objects.select_related.return_value.filter.return_value.exists.return_value = True
-
- result = _user_has_social_auth_record(user, enterprise_customer)
- assert result is True
-
- mock_tpa.provider.Registry.get.assert_called_once_with(provider_id='mock-idp')
- mock_user_social_auth.objects.select_related.assert_called_once_with('user')
- mock_user_social_auth.objects.select_related.return_value.filter.assert_called_once_with(
- provider__in=['mock-backend'], user=user
- )
-
- @mock.patch('openedx.features.enterprise_support.utils.UserSocialAuth')
- @mock.patch('openedx.features.enterprise_support.utils.third_party_auth')
- def test_user_has_social_auth_record_no_providers(self, mock_tpa, mock_user_social_auth):
- user = mock.Mock()
- enterprise_customer = {
- 'identity_providers': [],
- }
-
- result = _user_has_social_auth_record(user, enterprise_customer)
- assert result is False
-
- assert not mock_tpa.provider.Registry.get.called
- assert not mock_user_social_auth.objects.select_related.called
-
- @mock.patch('openedx.features.enterprise_support.utils.UserSocialAuth')
- @mock.patch('openedx.features.enterprise_support.utils.third_party_auth')
- def test_user_has_social_auth_record_no_enterprise_customer(self, mock_tpa, mock_user_social_auth):
- user = mock.Mock()
- enterprise_customer = None
-
- result = _user_has_social_auth_record(user, enterprise_customer)
- assert result is False
-
- assert not mock_tpa.provider.Registry.get.called
- assert not mock_user_social_auth.objects.select_related.called
-
-
-@override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
-@skip_unless_lms
-class TestCourseAccessed(SharedModuleStoreTestCase, CompletionWaffleTestMixin):
- """
- Test the course accessed functionality.
-
- """
- @classmethod
- def setUpClass(cls):
- """
- Creates a test course that can be used for non-destructive tests
- """
- # setUpClassAndTestData() already calls setUpClass on SharedModuleStoreTestCase
- # pylint: disable=super-method-not-called
- with super().setUpClassAndTestData():
- cls.course = cls.create_test_course()
-
- @classmethod
- def setUpTestData(cls): # pylint: disable=super-method-not-called
- """Set up and enroll our fake user in the course."""
- cls.user = UserFactory(password=TEST_PASSWORD)
- CourseEnrollment.enroll(cls.user, cls.course.id)
- cls.site = Site.objects.get_current()
-
- @classmethod
- def create_test_course(cls):
- """
- Creates a test course.
- """
- course = CourseFactory.create()
- with cls.store.bulk_operations(course.id):
- chapter = BlockFactory.create(category='chapter', parent_location=course.location)
- chapter2 = BlockFactory.create(category='chapter', parent_location=course.location)
- sequential = BlockFactory.create(category='sequential', parent_location=chapter.location)
- sequential2 = BlockFactory.create(category='sequential', parent_location=chapter.location)
- sequential3 = BlockFactory.create(category='sequential', parent_location=chapter2.location)
- sequential4 = BlockFactory.create(category='sequential', parent_location=chapter2.location)
- vertical = BlockFactory.create(category='vertical', parent_location=sequential.location)
- vertical2 = BlockFactory.create(category='vertical', parent_location=sequential2.location)
- vertical3 = BlockFactory.create(category='vertical', parent_location=sequential3.location)
- vertical4 = BlockFactory.create(category='vertical', parent_location=sequential4.location)
- course.children = [chapter, chapter2]
- chapter.children = [sequential, sequential2]
- chapter2.children = [sequential3, sequential4]
- sequential.children = [vertical]
- sequential2.children = [vertical2]
- sequential3.children = [vertical3]
- sequential4.children = [vertical4]
- if hasattr(cls, 'user'):
- CourseEnrollment.enroll(cls.user, course.id)
- return course
-
- def setUp(self):
- """
- Set up for the tests.
- """
- super().setUp()
- self.client.login(username=self.user.username, password=TEST_PASSWORD)
-
- @override_waffle_switch(ENABLE_COMPLETION_TRACKING_SWITCH, active=True)
- def complete_sequential(self, course, sequential):
- """
- Completes provided sequential.
- """
- course_key = CourseKey.from_string(str(course.id))
- # Fake a visit to sequence2/vertical2
- block_key = UsageKey.from_string(str(sequential.location))
- if block_key.course_key.run is None:
- # Old mongo keys must be annotated with course run info before calling submit_completion:
- block_key = block_key.replace(course_key=course_key)
- completion = 1.0
- BlockCompletion.objects.submit_completion(
- user=self.user,
- block_key=block_key,
- completion=completion
- )
-
- @override_settings(LMS_BASE='test_url:9999')
- def test_course_accessed_with_completion_api(self):
- """
- Tests the course accessed function with completion API functionality
- """
- self.override_waffle_switch(True)
-
- # Course tree
- course = self.course
- vertical1 = course.children[0].children[0].children[0]
-
- self.complete_sequential(self.course, vertical1)
- course_accessed = is_course_accessed(self.user, str(self.course.id))
- self.assertTrue(course_accessed) # noqa: PT009
diff --git a/openedx/features/enterprise_support/utils.py b/openedx/features/enterprise_support/utils.py
deleted file mode 100644
index cc6402e60492..000000000000
--- a/openedx/features/enterprise_support/utils.py
+++ /dev/null
@@ -1,490 +0,0 @@
-"""
-Utility methods for Enterprise
-"""
-
-
-import json
-
-from completion.exceptions import UnavailableCompletionData
-from completion.utilities import get_key_to_last_completed_block
-from crum import get_current_request
-from django.conf import settings
-from django.core.cache import cache
-from django.urls import NoReverseMatch, reverse
-from django.utils.translation import gettext as _
-from edx_django_utils.cache import TieredCache, get_cache_key
-from edx_toggles.toggles import WaffleFlag
-from enterprise.api.v1.serializers import EnterpriseCustomerBrandingConfigurationSerializer
-from enterprise.models import EnterpriseCustomer, EnterpriseCustomerUser
-from social_django.models import UserSocialAuth
-
-from common.djangoapps import third_party_auth
-from common.djangoapps.student.helpers import get_next_url_for_login_page
-from lms.djangoapps.branding.api import get_privacy_url
-from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
-from openedx.core.djangoapps.user_authn.cookies import standard_cookie_settings
-from openedx.core.djangolib.markup import HTML, Text
-
-ENTERPRISE_HEADER_LINKS = WaffleFlag('enterprise.enterprise_header_links', __name__) # pylint: disable=toggle-missing-annotation
-
-
-def get_data_consent_share_cache_key(user_id, course_id, enterprise_customer_uuid=None):
- """
- Returns cache key for data sharing consent needed against user_id, course_id and enterprise_customer_uuid
- """
- cache_key_params = dict(
- type='data_sharing_consent_needed',
- user_id=user_id,
- course_id=course_id,
- )
-
- if enterprise_customer_uuid:
- cache_key_params['enterprise_customer_uuid'] = enterprise_customer_uuid
-
- return get_cache_key(**cache_key_params)
-
-
-def get_is_enterprise_cache_key(user_id):
- """
- Returns cache key for the enterprise learner validation method needed against user_id.
- """
- return get_cache_key(type='is_enterprise_learner', user_id=user_id)
-
-
-def clear_data_consent_share_cache(user_id, course_id, enterprise_customer_uuid):
- """
- clears data_sharing_consent_needed cache
- """
- consent_cache_key = get_data_consent_share_cache_key(user_id, course_id, enterprise_customer_uuid)
- TieredCache.delete_all_tiers(consent_cache_key)
-
-
-def update_logistration_context_for_enterprise(request, context, enterprise_customer):
- """
- Take the processed context produced by the view, determine if it's relevant
- to a particular Enterprise Customer, and update it to include that customer's
- enterprise metadata.
-
- Arguments:
- request (HttpRequest): The request for the logistration page.
- context (dict): Context for logistration page.
- enterprise_customer (dict): data for enterprise customer
-
- """
- sidebar_context = {}
- if enterprise_customer:
- is_proxy_login = request.GET.get('proxy_login')
- sidebar_context = get_enterprise_sidebar_context(enterprise_customer, is_proxy_login)
-
- if sidebar_context:
- context['data']['registration_form_desc']['fields'] = enterprise_fields_only(
- context['data']['registration_form_desc']
- )
- context.update(sidebar_context)
- context['enable_enterprise_sidebar'] = True
- context['data']['hide_auth_warnings'] = True
- context['data']['enterprise_name'] = enterprise_customer['name']
- else:
- context['enable_enterprise_sidebar'] = False
-
- update_third_party_auth_context_for_enterprise(request, context, enterprise_customer)
-
-
-def get_enterprise_sidebar_context(enterprise_customer, is_proxy_login):
- """
- Get context information for enterprise sidebar for the given enterprise customer.
-
- Args:
- enterprise_customer (dict): customer data from enterprise-customer endpoint, cached
- is_proxy_login (bool): If True, use proxy login welcome template
-
- Returns: Enterprise Sidebar Context with the following key-value pairs.
- {
- 'enterprise_name': 'Enterprise Name',
- 'enterprise_logo_url': 'URL of the enterprise logo image',
- 'enterprise_branded_welcome_string': 'Human readable welcome message customized for the enterprise',
- 'platform_welcome_string': 'Human readable welcome message for an enterprise learner',
- }
- """
- platform_name = configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME)
-
- branding_configuration = enterprise_customer.get('branding_configuration', {})
- logo_url = branding_configuration.get('logo', '') if isinstance(branding_configuration, dict) else ''
-
- if is_proxy_login:
- branded_welcome_template = configuration_helpers.get_value(
- 'ENTERPRISE_PROXY_LOGIN_WELCOME_TEMPLATE',
- settings.ENTERPRISE_PROXY_LOGIN_WELCOME_TEMPLATE
- )
- else:
- branded_welcome_template = configuration_helpers.get_value(
- 'ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE',
- settings.ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE
- )
-
- branded_welcome_string = Text(branded_welcome_template).format(
- start_bold=HTML(''),
- end_bold=HTML(''),
- line_break=HTML('
'),
- enterprise_name=enterprise_customer['name'],
- platform_name=platform_name,
- privacy_policy_link_start=HTML("").format(
- pp_url=get_privacy_url()
- ),
- privacy_policy_link_end=HTML(""),
- )
-
- platform_welcome_template = configuration_helpers.get_value(
- 'ENTERPRISE_PLATFORM_WELCOME_TEMPLATE',
- settings.ENTERPRISE_PLATFORM_WELCOME_TEMPLATE
- )
- platform_welcome_string = platform_welcome_template.format(platform_name=platform_name)
-
- return {
- 'enterprise_name': enterprise_customer['name'],
- 'enterprise_logo_url': logo_url,
- 'enterprise_branded_welcome_string': branded_welcome_string,
- 'platform_welcome_string': platform_welcome_string,
- }
-
-
-def enterprise_fields_only(fields):
- """
- Take the received field definition, and exclude those fields that we don't want
- to require if the user is going to be a member of an Enterprise Customer.
- """
- enterprise_exclusions = configuration_helpers.get_value(
- 'ENTERPRISE_EXCLUDED_REGISTRATION_FIELDS',
- settings.ENTERPRISE_EXCLUDED_REGISTRATION_FIELDS
- )
- return [field for field in fields['fields'] if field['name'] not in enterprise_exclusions]
-
-
-def update_third_party_auth_context_for_enterprise(request, context, enterprise_customer=None):
- """
- Return updated context of third party auth with modified data for the given enterprise customer.
-
- Arguments:
- request (HttpRequest): The request for the logistration page.
- context (dict): Context for third party auth providers and auth pipeline.
- enterprise_customer (dict): data for enterprise customer
-
- Returns:
- context (dict): Updated context of third party auth with modified
- `errorMessage`.
- """
- if context['data']['third_party_auth']['errorMessage']:
- context['data']['third_party_auth']['errorMessage'] = Text(_(
- 'We are sorry, you are not authorized to access {platform_name} via this channel. '
- 'Please contact your learning administrator or manager in order to access {platform_name}.'
- '{line_break}{line_break}'
- 'Error Details:{line_break}{error_message}')
- ).format(
- platform_name=configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
- error_message=context['data']['third_party_auth']['errorMessage'],
- line_break=HTML('
')
- )
-
- if enterprise_customer:
- context['data']['third_party_auth']['providers'] = []
- context['data']['third_party_auth']['secondaryProviders'] = []
-
- running_pipeline = third_party_auth.pipeline.get(request)
- if running_pipeline is not None:
- current_provider = third_party_auth.provider.Registry.get_from_pipeline(running_pipeline)
- if current_provider is not None and current_provider.skip_registration_form and enterprise_customer:
- # For enterprise (and later for everyone), we need to get explicit consent to the
- # Terms of service instead of auto submitting the registration form outright.
- context['data']['third_party_auth']['autoSubmitRegForm'] = False
- context['data']['third_party_auth']['autoRegisterWelcomeMessage'] = Text(_(
- 'Thank you for joining {platform_name}. '
- 'Just a couple steps before you start learning!')
- ).format(
- platform_name=configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME)
- )
- context['data']['third_party_auth']['registerFormSubmitButtonText'] = _('Continue')
-
- return context
-
-
-def handle_enterprise_cookies_for_logistration(request, response, context):
- """
- Helper method for setting or deleting enterprise cookies on logistration response.
-
- Arguments:
- request (HttpRequest): The request for the logistration page.
- response (HttpResponse): The response for the logistration page.
- context (dict): Context for logistration page.
-
- """
- # This cookie can be used for tests or minor features,
- # but should not be used for payment related or other critical work
- # since users can edit their cookies
- _set_experiments_is_enterprise_cookie(request, response, context['enable_enterprise_sidebar'])
-
- # Remove enterprise cookie so that subsequent requests show default login page.
- response.delete_cookie(
- configuration_helpers.get_value('ENTERPRISE_CUSTOMER_COOKIE_NAME', settings.ENTERPRISE_CUSTOMER_COOKIE_NAME),
- domain=configuration_helpers.get_value('BASE_COOKIE_DOMAIN', settings.BASE_COOKIE_DOMAIN),
- )
-
-
-def _set_experiments_is_enterprise_cookie(request, response, experiments_is_enterprise):
- """ Sets the experiments_is_enterprise cookie on the response.
- This cookie can be used for tests or minor features,
- but should not be used for payment related or other critical work
- since users can edit their cookies
- """
- cookie_settings = standard_cookie_settings(request)
-
- response.set_cookie(
- 'experiments_is_enterprise',
- json.dumps(experiments_is_enterprise),
- **cookie_settings
- )
-
-
-def update_account_settings_context_for_enterprise(context, enterprise_customer, user):
- """
- Take processed context for account settings page and update it taking enterprise customer into account.
-
- Arguments:
- context (dict): Context for account settings page.
- enterprise_customer (dict): data for enterprise customer
- user (User): request user
- """
- enterprise_context = {
- 'enterprise_name': enterprise_customer['name'] if enterprise_customer else None,
- 'sync_learner_profile_data': _get_sync_learner_profile_data(enterprise_customer),
- 'edx_support_url': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK),
- 'enterprise_readonly_account_fields': {
- 'fields': list(get_enterprise_readonly_account_fields(user))
- }
- }
- context.update(enterprise_context)
-
-
-def get_enterprise_readonly_account_fields(user):
- """
- Returns a set of account fields that are read-only for enterprise users.
- """
- # TODO circular dependency between enterprise_support.api and enterprise_support.utils
- from openedx.features.enterprise_support.api import enterprise_customer_for_request
- enterprise_customer = enterprise_customer_for_request(get_current_request())
-
- enterprise_readonly_account_fields = list(settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS)
-
- # if user has no `UserSocialAuth` record then allow to edit `fullname`
- # whether the `sync_learner_profile_data` is enabled or disabled
- user_social_auth_record = _user_has_social_auth_record(user, enterprise_customer)
- if not user_social_auth_record and 'name' in enterprise_readonly_account_fields:
- enterprise_readonly_account_fields.remove('name')
-
- sync_learner_profile_data = _get_sync_learner_profile_data(enterprise_customer)
- return set(enterprise_readonly_account_fields) if sync_learner_profile_data else set()
-
-
-def _user_has_social_auth_record(user, enterprise_customer):
- """
- Return True if a `UserSocialAuth` record exists for `user` False otherwise.
- """
- provider_backend_names = []
- if enterprise_customer and enterprise_customer['identity_providers']:
- for idp in enterprise_customer['identity_providers']:
- identity_provider = third_party_auth.provider.Registry.get(
- provider_id=idp['provider_id']
- )
- if identity_provider and hasattr(identity_provider, 'backend_name'):
- provider_backend_names.append(identity_provider.backend_name)
-
- if provider_backend_names:
- return UserSocialAuth.objects.select_related('user').\
- filter(provider__in=provider_backend_names, user=user).exists()
- return False
-
-
-def _get_sync_learner_profile_data(enterprise_customer):
- """
- Returns whether the configuration of the given enterprise customer supports
- synching learner profile data.
- """
- if enterprise_customer:
- identity_provider = third_party_auth.provider.Registry.get(
- provider_id=enterprise_customer['identity_provider'],
- )
- if identity_provider:
- return identity_provider.sync_learner_profile_data
-
- return False
-
-
-def get_enterprise_learner_portal(request):
- """
- Gets the formatted portal name and slug that can be used
- to generate a link for an enabled enterprise Learner Portal.
-
- Caches and returns result in/from the user's request session if provided.
- """
- # Prevent a circular import.
- from openedx.features.enterprise_support.api import enterprise_customer_uuid_for_request, enterprise_enabled
-
- user = request.user
- # Only cache this if a learner is authenticated (AnonymousUser exists and should not be tracked)
-
- learner_portal_session_key = 'enterprise_learner_portal'
-
- if enterprise_enabled() and ENTERPRISE_HEADER_LINKS.is_enabled() and user and user.id:
- # If the key exists return that value
- if learner_portal_session_key in request.session:
- return json.loads(request.session[learner_portal_session_key])
-
- kwargs = {
- 'user_id': user.id,
- 'enterprise_customer__enable_learner_portal': True,
- }
- enterprise_customer_uuid = enterprise_customer_uuid_for_request(request)
- if enterprise_customer_uuid:
- kwargs['enterprise_customer__uuid'] = enterprise_customer_uuid
-
- queryset = EnterpriseCustomerUser.objects.filter(**kwargs).prefetch_related(
- 'enterprise_customer',
- 'enterprise_customer__branding_configuration',
- )
-
- if not enterprise_customer_uuid:
- # If the request doesn't help us know which Enterprise Customer UUID to select with,
- # order by the most recently activated/modified customers,
- # so that when we select the first result of the query as the preferred
- # customer, it's the most recently active one.
- queryset = queryset.order_by('-enterprise_customer__active', '-modified')
-
- preferred_enterprise_customer_user = queryset.first()
- if not preferred_enterprise_customer_user:
- return None
-
- enterprise_customer = preferred_enterprise_customer_user.enterprise_customer
- learner_portal_data = {
- 'name': enterprise_customer.name,
- 'slug': enterprise_customer.slug,
- 'logo': enterprise_branding_configuration(enterprise_customer).get('logo'),
- }
-
- # Cache the result in the user's request session
- request.session[learner_portal_session_key] = json.dumps(learner_portal_data)
- return learner_portal_data
- return None
-
-
-def enterprise_branding_configuration(enterprise_customer_obj):
- """
- Given an instance of ``EnterpriseCustomer``, returns a related
- branding_configuration serialized dictionary if it exists, otherwise
- the serialized default EnterpriseCustomerBrandingConfiguration object.
-
- EnterpriseCustomerBrandingConfigurationSerializer will use default values
- for any empty branding config fields.
- """
- branding_config = enterprise_customer_obj.safe_branding_configuration
- return EnterpriseCustomerBrandingConfigurationSerializer(branding_config).data
-
-
-def get_enterprise_learner_generic_name(request):
- """
- Get a generic name concatenating the Enterprise Customer name and 'Learner'.
-
- ENT-924: Temporary solution for hiding potentially sensitive SSO names.
- When a more complete solution is put in place, delete this function and all of its uses.
- """
- # Prevent a circular import. This function makes sense to be in this module though. And see function description.
- from openedx.features.enterprise_support.api import enterprise_customer_for_request
-
- # ENT-2626: For 404 pages we don't need to perform these actions.
- if getattr(request, 'view_name', None) == '404':
- return
-
- enterprise_customer = enterprise_customer_for_request(request)
-
- return (
- enterprise_customer['name'] + 'Learner'
- if enterprise_customer and enterprise_customer['replace_sensitive_sso_username']
- else ''
- )
-
-
-def is_enterprise_learner(user):
- """
- Check if the given user belongs to an enterprise. Cache the value if an enterprise learner is found.
-
- Arguments:
- user (User): Django User object or Django User object id.
-
- Returns:
- (bool): True if given user is an enterprise learner.
- """
- # Prevent a circular import.
- from openedx.features.enterprise_support.api import enterprise_enabled
-
- if not enterprise_enabled():
- return False
-
- try:
- user_id = int(user)
- except TypeError:
- user_id = user.id
- cached_is_enterprise_key = get_is_enterprise_cache_key(user_id)
- if cache.get(cached_is_enterprise_key):
- return True
-
- if EnterpriseCustomerUser.objects.filter(user_id=user_id).exists():
- # Cache the enterprise user for one hour.
- cache.set(cached_is_enterprise_key, True, 3600)
- return True
-
- return False
-
-
-def get_enterprise_slug_login_url():
- """
- Return the enterprise slug login's URL (enterprise/login) if it exists otherwise None
- """
- try:
- return reverse('enterprise_slug_login')
- except NoReverseMatch:
- return None
-
-
-def get_provider_login_url(request, provider_id, redirect_url=None):
- """
- Return the given provider's login URL.
-
- This method is here to avoid the importing of pipeline and student app in enterprise.
- """
-
- provider_login_url = third_party_auth.pipeline.get_login_url(
- provider_id,
- third_party_auth.pipeline.AUTH_ENTRY_LOGIN,
- redirect_url=redirect_url if redirect_url else get_next_url_for_login_page(request)
- )
- return provider_login_url
-
-
-def fetch_enterprise_customer_by_id(enterprise_uuid):
- return EnterpriseCustomer.objects.get(uuid=enterprise_uuid)
-
-
-def is_course_accessed(user, course_id):
- """
- Check if the learner accessed the course.
-
- Arguments:
- user (User): Django User object.
- course_id (String): The course identifier
-
- Returns:
- (bool): True if course has been accessed by the enterprise learner.
- """
- try:
- get_key_to_last_completed_block(user, course_id)
- return True
- except UnavailableCompletionData:
- return False
diff --git a/pyproject.toml b/pyproject.toml
index 52d2cb789126..e789e5629307 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -290,7 +290,7 @@ constraint-dependencies = [
"sphinx-autoapi<3.6.1",
"setuptools<82",
"astroid==4.0.4",
- "edx-enterprise==8.17.0",
+ "edx-enterprise==8.18.0",
"djangorestframework<3.18",
]
[tool.edx_lint]
@@ -407,7 +407,7 @@ uv_constraints = [
# The team that owns this package will manually bump this package rather than
# having it pulled in automatically. This is to allow them to better control its
# deployment and to do it in a process that works better for them.
- "edx-enterprise==8.17.0",
+ "edx-enterprise==8.18.0",
# Date: 2026-08-31
# DRF 3.18.0 changes many=True validation errors from a list to a dict keyed by
# item index, which breaks the error response shape of several write endpoints.
@@ -773,12 +773,6 @@ ignore_imports = [
# -> openedx.core.djangoapps.content.learning_sequences.api.permissions
# -> lms.djangoapps.courseware.access
"openedx.core.djangoapps.content.learning_sequences.api.permissions -> lms.djangoapps.courseware.access",
- # cms.djangoapps.contentstore.[various]
- # -> openedx.features.content_type_gating.partitions
- # -> openedx.features.discounts.utils
- # -> openedx.features.discounts.applicability
- # -> openedx.features.enterprise_support.utils
- "openedx.features.enterprise_support.utils -> lms.djangoapps.branding.api",
"cms.djangoapps.contentstore.rest_api.v1.views.settings -> lms.djangoapps.certificates.api",
# We are ignoring this existing import until we can refactor contenstore/helpers.
# https://github.com/openedx/edx-platform/issues/37637
diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt
index 6fbf35818c5a..16b8ba31246d 100644
--- a/requirements/edx/base.txt
+++ b/requirements/edx/base.txt
@@ -480,7 +480,7 @@ edx-drf-extensions==10.9.0
# openedx-authz
# openedx-core
# openedx-platform
-edx-enterprise==8.17.0
+edx-enterprise==8.18.0
# via openedx-platform
edx-event-bus-kafka==6.1.0
# via openedx-platform
diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt
index 7c99780f701d..359fc09debc7 100644
--- a/requirements/edx/development.txt
+++ b/requirements/edx/development.txt
@@ -533,7 +533,7 @@ edx-drf-extensions==10.9.0
# openedx-authz
# openedx-core
# openedx-platform
-edx-enterprise==8.17.0
+edx-enterprise==8.18.0
# via openedx-platform
edx-event-bus-kafka==6.1.0
# via openedx-platform
diff --git a/uv.lock b/uv.lock
index 0fce085ea96a..364d8d2cab0f 100644
--- a/uv.lock
+++ b/uv.lock
@@ -20,7 +20,7 @@ constraints = [
{ name = "django-oauth-toolkit", specifier = "==1.7.1" },
{ name = "django-stubs", specifier = "<6" },
{ name = "djangorestframework", specifier = "<3.18" },
- { name = "edx-enterprise", specifier = "==8.17.0" },
+ { name = "edx-enterprise", specifier = "==8.18.0" },
{ name = "elasticsearch", specifier = "==7.9.1" },
{ name = "libsass", specifier = "==0.10.0" },
{ name = "lxml", specifier = "==5.3.2" },
@@ -2024,7 +2024,7 @@ wheels = [
[[package]]
name = "edx-enterprise"
-version = "8.17.0"
+version = "8.18.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "bleach" },