def get_enterprise_learner_portals(request): """ Gets the formatted portal names and slugs that can be used to generate links for enabled enterprise Learner Portals. Caches and returns results in/from the user's request session if provided. """ # Prevent a circular import. from openedx.features.enterprise_support.api import enterprise_enabled if enterprise_enabled(): # If the key exists return that value if 'enterprise_learner_portals' in request.session: return json.loads(request.session['enterprise_learner_portals']) user = request.user # Ordering is important, this is consistent with how we decide on which # enterprise_customer is the selected one for an enterprise_customer enterprise_learner_portals = [{ 'name': enterprise_customer_user.enterprise_customer.name, 'slug': enterprise_customer_user.enterprise_customer.slug, 'logo': enterprise_customer_user.enterprise_customer.branding_configuration.logo.url, } for enterprise_customer_user in EnterpriseCustomerUser.objects.filter( user_id=user.id, enterprise_customer__enable_learner_portal=True ).prefetch_related( 'enterprise_customer', 'enterprise_customer__branding_configuration' ).order_by('-enterprise_customer__active', '-modified')] # Cache the result in the user's request session request.session['enterprise_learner_portals'] = json.dumps(enterprise_learner_portals) return enterprise_learner_portals return None
def test_utils_with_enterprise_disabled(self): """ Test that disabling the enterprise integration flag causes the utilities to return the expected default values. """ self.assertFalse(enterprise_enabled()) self.assertEqual(insert_enterprise_pipeline_elements(None), None)
def get(self, request, username_or_email): """ Returns a list of enrollments for the given user, along with information about previous manual enrollment changes. """ try: user = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email)) except User.DoesNotExist: return JsonResponse([]) enrollments = get_enrollments(user.username, include_inactive=True) for enrollment in enrollments: # Folds the course_details field up into the main JSON object. enrollment.update(**enrollment.pop('course_details')) course_key = CourseKey.from_string(enrollment['course_id']) # Get the all courses modes and replace with existing modes. enrollment['course_modes'] = self.get_course_modes(course_key) # Add the price of the course's verified mode. self.include_verified_mode_info(enrollment, course_key) # Add manual enrollment history, if it exists enrollment['manual_enrollment'] = self.manual_enrollment_data(enrollment, course_key) if enterprise_enabled(): enterprise_enrollments_by_course_id = self._enterprise_course_enrollments_by_course_id(user) for enrollment in enrollments: enterprise_course_enrollments = enterprise_enrollments_by_course_id.get(enrollment['course_id'], []) enrollment['enterprise_course_enrollments'] = enterprise_course_enrollments return JsonResponse(enrollments)
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() assert insert_enterprise_pipeline_elements(None) is None
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_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_enabled, enterprise_customer_uuid_for_request 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 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 enterprise_enabled() and is_enterprise_learner(user): kwargs = { 'username': six.text_type(user.username), 'course_run_id': six.text_type(course_id) } transmit_single_learner_data.apply_async(kwargs=kwargs)
def test_utils_with_enterprise_enabled(self): """ Test that enabling enterprise integration (which is currently on by default) causes the the utilities to return the expected values. """ self.assertTrue(enterprise_enabled()) pipeline = ['abc', 'social_core.pipeline.social_auth.load_extra_data', 'def'] insert_enterprise_pipeline_elements(pipeline) self.assertEqual(pipeline, ['abc', 'enterprise.tpa_pipeline.handle_enterprise_logistration', 'social_core.pipeline.social_auth.load_extra_data', 'def'])
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) self.assertTrue(enterprise_enabled()) response = self.client.get(url, HTTP_HOST='testserver') expected = { 'enterprise_target': enterprise_target, } self.assertDictContainsSubset(expected, response.context_data) if enterprise_target: self.assertContains(response, 'We are signing you in.')
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 enterprise_enabled() and 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)
class SettingsUnitTest(testutil.TestCase): """Unit tests for settings management code.""" # Allow access to protected methods (or module-protected methods) under test. # pylint: disable=protected-access # Suppress sprurious no-member warning on fakes. # pylint: disable=no-member def setUp(self): super(SettingsUnitTest, self).setUp() self.settings = testutil.FakeDjangoSettings(_SETTINGS_MAP) def test_apply_settings_adds_exception_middleware(self): settings.apply_settings(self.settings) for middleware_name in settings._MIDDLEWARE_CLASSES: self.assertIn(middleware_name, self.settings.MIDDLEWARE_CLASSES) def test_apply_settings_adds_fields_stored_in_session(self): settings.apply_settings(self.settings) self.assertEqual(settings._FIELDS_STORED_IN_SESSION, self.settings.FIELDS_STORED_IN_SESSION) @unittest.skipUnless(testutil.AUTH_FEATURE_ENABLED, 'third_party_auth not enabled') def test_apply_settings_enables_no_providers_by_default(self): # Providers are only enabled via ConfigurationModels in the database settings.apply_settings(self.settings) self.assertEqual([], provider.Registry.enabled()) def test_apply_settings_turns_off_raising_social_exceptions(self): # Guard against submitting a conf change that's convenient in dev but # bad in prod. settings.apply_settings(self.settings) self.assertFalse(self.settings.SOCIAL_AUTH_RAISE_EXCEPTIONS) @unittest.skipUnless(enterprise_enabled(), 'enterprise not enabled') def test_enterprise_elements_inserted(self): settings.apply_settings(self.settings) self.assertIn('enterprise.tpa_pipeline.handle_enterprise_logistration', self.settings.SOCIAL_AUTH_PIPELINE)
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.api import enterprise_enabled from openedx.features.enterprise_support.utils import is_enterprise_learner context = {} if enterprise_enabled() and 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
def post(self, request): """Enrolls the currently logged-in user in a course. Server-to-server calls may deactivate or modify the mode of existing enrollments. All other requests go through `add_enrollment()`, which allows creation of new and reactivation of old enrollments. """ # Get the User, Course ID, and Mode from the request. username = request.data.get('user', request.user.username) course_id = request.data.get('course_details', {}).get('course_id') if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Course ID must be specified to create a new enrollment."} ) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) mode = request.data.get('mode') has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. if not username: username = request.user.username if username != request.user.username and not has_api_key_permissions: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) if mode not in (CourseMode.AUDIT, CourseMode.HONOR, None) and not has_api_key_permissions: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": u"User does not have permission to create enrollment with mode [{mode}].".format( mode=mode ) } ) 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: return Response( status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(username) } ) embargo_response = embargo_api.get_embargo_response(request, course_id, user) if embargo_response: return embargo_response try: is_active = request.data.get('is_active') # Check if the requested activation status is None or a Boolean if is_active is not None and not isinstance(is_active, bool): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': (u"'{value}' is an invalid enrollment activation status.").format(value=is_active) } ) explicit_linked_enterprise = request.data.get('linked_enterprise_customer') if explicit_linked_enterprise and has_api_key_permissions and enterprise_enabled(): enterprise_api_client = EnterpriseApiServiceClient() consent_client = ConsentApiServiceClient() try: enterprise_api_client.post_enterprise_course_enrollment(username, unicode(course_id), None) except EnterpriseApiException as error: log.exception("An unexpected error occurred while creating the new EnterpriseCourseEnrollment " "for user [%s] in course run [%s]", username, course_id) raise CourseEnrollmentError(text_type(error)) kwargs = { 'username': username, 'course_id': unicode(course_id), 'enterprise_customer_uuid': explicit_linked_enterprise, } consent_client.provide_consent(**kwargs) enrollment_attributes = request.data.get('enrollment_attributes') enrollment = api.get_enrollment(username, unicode(course_id)) mode_changed = enrollment and mode is not None and enrollment['mode'] != mode active_changed = enrollment and is_active is not None and enrollment['is_active'] != is_active missing_attrs = [] if enrollment_attributes: actual_attrs = [ u"{namespace}:{name}".format(**attr) for attr in enrollment_attributes ] missing_attrs = set(REQUIRED_ATTRIBUTES.get(mode, [])) - set(actual_attrs) if has_api_key_permissions and (mode_changed or active_changed): if mode_changed and active_changed and not is_active: # if the requester wanted to deactivate but specified the wrong mode, fail # the request (on the assumption that the requester had outdated information # about the currently active enrollment). msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format( enrollment["mode"], mode ) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) if len(missing_attrs) > 0: msg = u"Missing enrollment attributes: requested mode={} required attributes={}".format( mode, REQUIRED_ATTRIBUTES.get(mode) ) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) response = api.update_enrollment( username, unicode(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes, # If we are updating enrollment by authorized api caller, we should allow expired modes include_expired=has_api_key_permissions ) else: # Will reactivate inactive enrollments. response = api.add_enrollment( username, unicode(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes ) cohort_name = request.data.get('cohort') if cohort_name is not None: cohort = get_cohort_by_name(course_id, cohort_name) try: add_user_to_cohort(cohort, user) except ValueError: # user already in cohort, probably because they were un-enrolled and re-enrolled log.exception('Cohort re-addition') email_opt_in = request.data.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) log.info('The user [%s] has already been enrolled in course run [%s].', username, course_id) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"The [{mode}] course mode is expired or otherwise unavailable for course run [{course_id}]." ).format(mode=mode, course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) except CourseEnrollmentExistsError as error: log.warning('An enrollment already exists for user [%s] in course run [%s].', username, course_id) return Response(data=error.enrollment) except CourseEnrollmentError: log.exception("An error occurred while creating the new course enrollment for user " "[%s] in course run [%s]", username, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"An error occurred while creating the new course enrollment for user " u"'{username}' in course '{course_id}'" ).format(username=username, course_id=course_id) } ) except CourseUserGroup.DoesNotExist: log.exception('Missing cohort [%s] in course run [%s]', cohort_name, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": "An error occured while adding to cohort [%s]" % cohort_name }) finally: # Assumes that the ecommerce service uses an API key to authenticate. if has_api_key_permissions: current_enrollment = api.get_enrollment(username, unicode(course_id)) audit_log( 'enrollment_change_requested', course_id=unicode(course_id), requested_mode=mode, actual_mode=current_enrollment['mode'] if current_enrollment else None, requested_activation=is_active, actual_activation=current_enrollment['is_active'] if current_enrollment else None, user_id=user.id )
def __init__(self): """ We don't need to use this middleware if the Enterprise feature isn't enabled. """ if not api.enterprise_enabled(): raise MiddlewareNotUsed()
url(r'^debug/show_parameters$', 'debug.views.show_parameters'), ) # Third-party auth. if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'): urlpatterns += ( url(r'', include('third_party_auth.urls')), url(r'api/third_party_auth/', include('third_party_auth.api.urls')), # NOTE: The following login_oauth_token endpoint is DEPRECATED. # Please use the exchange_access_token endpoint instead. url(r'^login_oauth_token/(?P<backend>[^/]+)/$', 'student.views.login_oauth_token'), ) # Enterprise if enterprise_enabled(): urlpatterns += ( url(r'', include('enterprise.urls')), ) # OAuth token exchange if settings.FEATURES.get('ENABLE_OAUTH2_PROVIDER'): urlpatterns += ( url( r'^oauth2/login/$', LoginWithAccessTokenView.as_view(), name="login_with_access_token" ), ) # Certificates
def login_and_registration_form(request, initial_mode="login"): """Render the combined login/registration form, defaulting to login This relies on the JS to asynchronously load the actual form from the user_api. Keyword Args: initial_mode (string): Either "login" or "register". """ # Determine the URL to redirect to following login/registration/third_party_auth redirect_to = get_next_url_for_login_page(request) # If we're already logged in, redirect to the dashboard # Note: If the session is valid, we update all logged_in cookies(in particular JWTs) # since Django's SessionAuthentication middleware auto-updates session cookies but not # the other login-related cookies. See ARCH-282 and ARCHBOM-1718 if request.user.is_authenticated: response = redirect(redirect_to) response = set_logged_in_cookies(request, response, request.user) return response # Retrieve the form descriptions from the user API form_descriptions = _get_form_descriptions(request) # Our ?next= URL may itself contain a parameter 'tpa_hint=x' that we need to check. # If present, we display a login page focused on third-party auth with that provider. third_party_auth_hint = None if '?' in redirect_to: # lint-amnesty, pylint: disable=too-many-nested-blocks try: next_args = urllib.parse.parse_qs( urllib.parse.urlparse(redirect_to).query) if 'tpa_hint' in next_args: provider_id = next_args['tpa_hint'][0] tpa_hint_provider = third_party_auth.provider.Registry.get( provider_id=provider_id) if tpa_hint_provider: if tpa_hint_provider.skip_hinted_login_dialog: # Forward the user directly to the provider's login URL when the provider is configured # to skip the dialog. if initial_mode == "register": auth_entry = pipeline.AUTH_ENTRY_REGISTER else: auth_entry = pipeline.AUTH_ENTRY_LOGIN return redirect( pipeline.get_login_url(provider_id, auth_entry, redirect_url=redirect_to)) third_party_auth_hint = provider_id initial_mode = "hinted_login" except (KeyError, ValueError, IndexError) as ex: log.exception("Unknown tpa_hint provider: %s", ex) # Redirect to authn MFE if it is enabled or user is not an enterprise user or not coming from a SAML IDP. saml_provider = False running_pipeline = pipeline.get(request) enterprise_customer = enterprise_customer_for_request(request) if running_pipeline: saml_provider, __ = third_party_auth.utils.is_saml_provider( running_pipeline.get('backend'), running_pipeline.get('kwargs')) if should_redirect_to_authn_microfrontend( ) and not enterprise_customer and not saml_provider: # This is to handle a case where a logged-in cookie is not present but the user is authenticated. # Note: If we don't handle this learner is redirected to authn MFE and then back to dashboard # instead of the desired redirect URL (e.g. finish_auth) resulting in learners not enrolling # into the courses. if request.user.is_authenticated and redirect_to: return redirect(redirect_to) query_params = request.GET.urlencode() url_path = '/{}{}'.format(initial_mode, '?' + query_params if query_params else '') return redirect(settings.AUTHN_MICROFRONTEND_URL + url_path) # Account activation message account_activation_messages = [{ 'message': message.message, 'tags': message.tags } for message in messages.get_messages(request) if 'account-activation' in message.tags] account_recovery_messages = [{ 'message': message.message, 'tags': message.tags } for message in messages.get_messages(request) if 'account-recovery' in message.tags] # Otherwise, render the combined login/registration page context = { 'data': { 'login_redirect_url': redirect_to, 'initial_mode': initial_mode, 'third_party_auth': third_party_auth_context(request, redirect_to, third_party_auth_hint), 'third_party_auth_hint': third_party_auth_hint or '', 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'support_link': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK), 'password_reset_support_link': configuration_helpers.get_value( 'PASSWORD_RESET_SUPPORT_LINK', settings.PASSWORD_RESET_SUPPORT_LINK) or settings.SUPPORT_SITE_LINK, 'account_activation_messages': account_activation_messages, 'account_recovery_messages': account_recovery_messages, # Include form descriptions retrieved from the user API. # We could have the JS client make these requests directly, # but we include them in the initial page load to avoid # the additional round-trip to the server. 'login_form_desc': json.loads(form_descriptions['login']), 'registration_form_desc': json.loads(form_descriptions['registration']), 'password_reset_form_desc': json.loads(form_descriptions['password_reset']), 'account_creation_allowed': configuration_helpers.get_value( 'ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)), 'is_account_recovery_feature_enabled': is_secondary_email_feature_enabled(), 'is_multiple_user_enterprises_feature_enabled': is_multiple_user_enterprises_feature_enabled(), 'enterprise_slug_login_url': get_enterprise_slug_login_url(), 'is_enterprise_enable': enterprise_enabled(), 'is_require_third_party_auth_enabled': is_require_third_party_auth_enabled(), }, 'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in header 'responsive': True, 'allow_iframing': True, 'disable_courseware_js': True, 'combined_login_and_register': True, 'disable_footer': not configuration_helpers.get_value( 'ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER', settings.FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER']), } update_logistration_context_for_enterprise(request, context, enterprise_customer) response = render_to_response('student_account/login_and_register.html', context) handle_enterprise_cookies_for_logistration(request, response, context) return response
def post(self, request): """Enrolls the currently logged-in user in a course. Server-to-server calls may deactivate or modify the mode of existing enrollments. All other requests go through `add_enrollment()`, which allows creation of new and reactivation of old enrollments. """ # Get the User, Course ID, and Mode from the request. username = request.data.get('user', request.user.username) course_id = request.data.get('course_details', {}).get('course_id') if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, data={"message": u"Course ID must be specified to create a new enrollment."} ) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) mode = request.data.get('mode') has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. if not username: username = request.user.username if username != request.user.username and not has_api_key_permissions: # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) 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: return Response( status=status.HTTP_406_NOT_ACCEPTABLE, data={ 'message': u'The user {} does not exist.'.format(username) } ) can_vip_enroll = False if settings.FEATURES.get('ENABLE_MEMBERSHIP_INTEGRATION'): from membership.models import VIPCourseEnrollment can_vip_enroll = VIPCourseEnrollment.can_vip_enroll(user, course_id) is_ecommerce_request = mode not in (CourseMode.AUDIT, CourseMode.HONOR, None) if is_ecommerce_request and not has_api_key_permissions and not can_vip_enroll: return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": u"User does not have permission to create enrollment with mode [{mode}].".format( mode=mode ) } ) embargo_response = embargo_api.get_embargo_response(request, course_id, user) if embargo_response: return embargo_response try: is_active = request.data.get('is_active') # Check if the requested activation status is None or a Boolean if is_active is not None and not isinstance(is_active, bool): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'message': (u"'{value}' is an invalid enrollment activation status.").format(value=is_active) } ) explicit_linked_enterprise = request.data.get('linked_enterprise_customer') if explicit_linked_enterprise and has_api_key_permissions and enterprise_enabled(): enterprise_api_client = EnterpriseApiServiceClient() consent_client = ConsentApiServiceClient() try: enterprise_api_client.post_enterprise_course_enrollment(username, unicode(course_id), None) except EnterpriseApiException as error: log.exception("An unexpected error occurred while creating the new EnterpriseCourseEnrollment " "for user [%s] in course run [%s]", username, course_id) raise CourseEnrollmentError(text_type(error)) kwargs = { 'username': username, 'course_id': unicode(course_id), 'enterprise_customer_uuid': explicit_linked_enterprise, } consent_client.provide_consent(**kwargs) enrollment_attributes = request.data.get('enrollment_attributes') enrollment = api.get_enrollment(username, unicode(course_id)) mode_changed = enrollment and mode is not None and enrollment['mode'] != mode active_changed = enrollment and is_active is not None and enrollment['is_active'] != is_active missing_attrs = [] audit_with_order = False if enrollment_attributes: actual_attrs = [ u"{namespace}:{name}".format(**attr) for attr in enrollment_attributes ] missing_attrs = set(REQUIRED_ATTRIBUTES.get(mode, [])) - set(actual_attrs) audit_with_order = mode == 'audit' and 'order:order_number' in actual_attrs # Remove audit_with_order when no longer needed - implemented for REV-141 if has_api_key_permissions and (mode_changed or active_changed or audit_with_order): if mode_changed and active_changed and not is_active: # if the requester wanted to deactivate but specified the wrong mode, fail # the request (on the assumption that the requester had outdated information # about the currently active enrollment). msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format( enrollment["mode"], mode ) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) if len(missing_attrs) > 0: msg = u"Missing enrollment attributes: requested mode={} required attributes={}".format( mode, REQUIRED_ATTRIBUTES.get(mode) ) log.warning(msg) return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg}) response = api.update_enrollment( username, unicode(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes, # If we are updating enrollment by authorized api caller, we should allow expired modes include_expired=has_api_key_permissions ) else: # Will reactivate inactive enrollments. response = api.add_enrollment( username, unicode(course_id), mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes, user=user, is_ecommerce_request=is_ecommerce_request ) cohort_name = request.data.get('cohort') if cohort_name is not None: cohort = get_cohort_by_name(course_id, cohort_name) add_user_to_cohort(cohort, user) email_opt_in = request.data.get('email_opt_in', None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) log.info('The user [%s] has already been enrolled in course run [%s].', username, course_id) return Response(response) except CourseModeNotFoundError as error: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"The [{mode}] course mode is expired or otherwise unavailable for course run [{course_id}]." ).format(mode=mode, course_id=course_id), "course_details": error.data }) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id) } ) except CourseEnrollmentExistsError as error: log.warning('An enrollment already exists for user [%s] in course run [%s].', username, course_id) return Response(data=error.enrollment) except CourseEnrollmentError: log.exception("An error occurred while creating the new course enrollment for user " "[%s] in course run [%s]", username, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": ( u"An error occurred while creating the new course enrollment for user " u"'{username}' in course '{course_id}'" ).format(username=username, course_id=course_id) } ) except CourseUserGroup.DoesNotExist: log.exception('Missing cohort [%s] in course run [%s]', cohort_name, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, data={ "message": "An error occured while adding to cohort [%s]" % cohort_name }) finally: # Assumes that the ecommerce service uses an API key to authenticate. if has_api_key_permissions: current_enrollment = api.get_enrollment(username, unicode(course_id)) audit_log( 'enrollment_change_requested', course_id=unicode(course_id), requested_mode=mode, actual_mode=current_enrollment['mode'] if current_enrollment else None, requested_activation=is_active, actual_activation=current_enrollment['is_active'] if current_enrollment else None, user_id=user.id )
url(r'^debug/show_parameters$', debug_views.show_parameters), ] # Third-party auth. if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'): urlpatterns += [ url(r'', include('third_party_auth.urls')), url(r'api/third_party_auth/', include('third_party_auth.api.urls')), # NOTE: The following login_oauth_token endpoint is DEPRECATED. # Please use the exchange_access_token endpoint instead. url(r'^login_oauth_token/(?P<backend>[^/]+)/$', student_views.login_oauth_token), ] # Enterprise if enterprise_enabled(): urlpatterns += [ url(r'', include('enterprise.urls')), ] # OAuth token exchange if settings.FEATURES.get('ENABLE_OAUTH2_PROVIDER'): urlpatterns += [ url(r'^oauth2/login/$', LoginWithAccessTokenView.as_view(), name='login_with_access_token'), ] # Certificates urlpatterns += [ url(
url(r'^debug/show_parameters$', debug_views.show_parameters), ] # Third-party auth. if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'): urlpatterns += [ url(r'api/third_party_auth/', include('third_party_auth.api.urls')), ] if settings.FEATURES.get('ENABLE_VIEWS'): urlpatterns += [ url(r'', include('third_party_auth.urls')), ] # Enterprise if enterprise_enabled() and settings.FEATURES.get('ENABLE_VIEWS'): urlpatterns += [ url(r'', include('enterprise.urls')), ] # OAuth token exchange if settings.FEATURES.get('ENABLE_OAUTH2_PROVIDER'): urlpatterns += [ url(r'^oauth2/login/$', LoginWithAccessTokenView.as_view(), name='login_with_access_token'), ] # Certificates urlpatterns += [ url(