def test_get_ec_for_request(self): """ Test that get_ec_for_request works. """ request = mock.MagicMock(session={'partial_pipeline': 'pipeline_key'}) with mock.patch('enterprise.tpa_pipeline.Registry') as fake_registry: fake_provider = mock.MagicMock() fake_provider.provider_id = 'provider_slug' fake_registry.get_from_pipeline.return_value = fake_provider assert get_enterprise_customer_for_request(request) == self.customer with raises(NotConnectedToEdX) as excinfo: get_enterprise_customer_for_request(request) expected_msg = "This package must be installed in an EdX environment to look up third-party auth providers." assert str(excinfo.value) == expected_msg
def add_data_sharing_consent_field(request, form_desc): """ Adds a checkbox field to be selected if the user consents to share data with the EnterpriseCustomer attached to the SSO provider with which they're authenticating. """ enterprise_customer = get_enterprise_customer_for_request(request) required = data_sharing_consent_required_at_login(request) if not data_sharing_consent_requested(request): return label = _( "I agree to allow {platform_name} to share data about my enrollment, " "completion and performance in all {platform_name} courses and programs " "where my enrollment is sponsored by {ec_name}." ).format( platform_name=configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME), ec_name=enterprise_customer.name ) error_msg = _( "To link your account with {ec_name}, you are required to consent to data sharing." ).format( ec_name=enterprise_customer.name ) form_desc.add_field( "data_sharing_consent", label=label, field_type="checkbox", default=False, required=required, error_messages={"required": error_msg}, )
def get(self, request): """ Render a form to collect user input about data sharing consent. """ # Verify that all necessary resources are present verify_edx_resources() # Get the OpenEdX platform name platform_name = configuration_helpers.get_value( "PLATFORM_NAME", settings.PLATFORM_NAME) # Get the EnterpriseCustomer for the request; raise an error if there isn't one. customer = get_enterprise_customer_for_request(request) if customer is None: raise Http404 # Quarantine the user to this module. self.quarantine(request) required = customer.enforces_data_sharing_consent( EnterpriseCustomer.AT_LOGIN) data = { 'platform_name': platform_name, 'sso_provider': customer.name, 'data_sharing_consent': 'required' if required else 'optional', "messages": { "warning": self.get_warning(customer.name, platform_name, required), "note": self.get_note(customer.name, required), }, } return render_to_response('grant_data_sharing_permissions.html', data, request=request)
def add_data_sharing_consent_field(request, form_desc): """ Adds a checkbox field to be selected if the user consents to share data with the EnterpriseCustomer attached to the SSO provider with which they're authenticating. """ enterprise_customer = get_enterprise_customer_for_request(request) required = data_sharing_consent_required_at_login(request) if not data_sharing_consent_requested(request): return label = _( "I agree to allow {platform_name} to share data about my enrollment, " "completion and performance in all {platform_name} courses and programs " "where my enrollment is sponsored by {ec_name}." ).format( platform_name=configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME), ec_name=enterprise_customer.name ) error_msg = _( "To link your account with {ec_name}, you are required to consent to data sharing." ).format( ec_name=enterprise_customer.name ) form_desc.add_field( "data_sharing_consent", label=label, field_type="checkbox", default=False, required=required, error_messages={"required": error_msg}, )
def enterprise_customer_for_request(request, tpa_hint=None): """ Check all the context clues of the request to determine if the request being made is tied to a particular EnterpriseCustomer. """ if not enterprise_enabled(): return None ec = get_enterprise_customer_for_request(request) if not ec and tpa_hint: try: ec = EnterpriseCustomer.objects.get( enterprise_customer_identity_provider__provider_id=tpa_hint) except EnterpriseCustomer.DoesNotExist: pass ec_uuid = request.GET.get('enterprise_customer') or request.COOKIES.get( settings.ENTERPRISE_CUSTOMER_COOKIE_NAME) if not ec and ec_uuid: try: ec = EnterpriseCustomer.objects.get(uuid=ec_uuid) except (EnterpriseCustomer.DoesNotExist, ValueError): ec = None return ec
def get_account_consent(self, request): """ Render a form to collect consent for account-wide data sharing. This method is called when no course ID is passed as a URL parameter; a form will be rendered with messaging around the concept of granting consent for the entire platform. """ # Get the OpenEdX platform name platform_name = configuration_helpers.get_value( "PLATFORM_NAME", settings.PLATFORM_NAME) # Get the EnterpriseCustomer for the request; raise an error if there isn't one. customer = get_enterprise_customer_for_request(request) if customer is None: raise Http404 # Quarantine the user to this module. self.quarantine(request) failure_url = request.GET.get('failure_url') context_data = self.get_default_context(customer, platform_name) account_specific_context = { 'consent_request_prompt': _('To log in using this SSO identity provider and access special course offers, you must first ' 'consent to share your learning achievements with {enterprise_customer_name}.' ).format(enterprise_customer_name=customer.name), 'confirmation_alert_prompt': _('In order to sign in and access special offers, you must consent to share your ' 'course data with {enterprise_customer_name}.').format( enterprise_customer_name=customer.name), 'page_language': get_language_from_request(request), 'platform_name': platform_name, 'enterprise_customer_name': customer.name, "course_id": None, "course_specific": False, 'enrollment_deferred': False, 'failure_url': failure_url, 'requested_permissions': [ _('your enrollment in all sponsored courses'), _('your learning progress'), _('course completion'), ] } context_data.update(account_specific_context) return render_to_response('grant_data_sharing_permissions.html', context_data, request=request)
def test_get_ec_for_request(self, fake_pipeline): """ Test that get_ec_for_request works. """ request = mock.MagicMock() fake_provider = mock.MagicMock() fake_provider.provider_id = 'provider_slug' fake_pipeline.return_value = { 'kwargs': { 'access_token': 'dummy' }, 'backend': 'fake_backend' } with mock.patch('enterprise.tpa_pipeline.Registry') as fake_registry: fake_registry.get_from_pipeline.return_value = fake_provider assert get_enterprise_customer_for_request( request) == self.customer with raises(NotConnectedToOpenEdX) as excinfo: get_enterprise_customer_for_request(request) self.assertIsNotNone(excinfo.value)
def post_account_consent(self, request, consent_provided): """ Interpret the account-wide form above, and save it to a UserDataSharingConsentAudit object for later retrieval. """ self.lift_quarantine(request) # Load the linked EnterpriseCustomer for this request. customer = get_enterprise_customer_for_request(request) if customer is None: # If we can't get an EnterpriseCustomer from the pipeline, then we don't really # have enough state to do anything meaningful. Just send the user to the login # screen; if they want to sign in with an Enterprise-linked SSO, they can do # so, and the pipeline will get them back here if they need to be. return redirect('signin_user') # Attempt to retrieve a user being manipulated by the third-party auth # pipeline. Return a 404 if no such user exists. social_auth = get_real_social_auth_object(request) user = getattr(social_auth, 'user', None) if user is None: raise Http404 if not consent_provided and active_provider_enforces_data_sharing( request, EnterpriseCustomer.AT_LOGIN): # Flush the session to avoid the possibility of accidental login and to abort the pipeline. # pipeline is flushed only if data sharing is enforced, in other cases let the user to login. request.session.flush() failure_url = request.POST.get('failure_url') or reverse( 'dashboard') return redirect(failure_url) ec_user, __ = EnterpriseCustomerUser.objects.get_or_create( user_id=user.id, enterprise_customer=customer, ) UserDataSharingConsentAudit.objects.update_or_create( user=ec_user, defaults={ 'state': (UserDataSharingConsentAudit.ENABLED if consent_provided else UserDataSharingConsentAudit.DISABLED) }) # Resume auth pipeline backend_name = request.session.get('partial_pipeline', {}).get('backend') return redirect(get_complete_url(backend_name))
def post_account_consent(self, request, consent_provided): """ Interpret the account-wide form above, and save it to a UserDataSharingConsentAudit object for later retrieval. """ self.lift_quarantine(request) # Load the linked EnterpriseCustomer for this request. Return a 404 if no such EnterpriseCustomer exists customer = get_enterprise_customer_for_request(request) if customer is None: raise Http404 # Attempt to retrieve a user being manipulated by the third-party auth # pipeline. Return a 404 if no such user exists. social_auth = get_real_social_auth_object(request) user = getattr(social_auth, 'user', None) if user is None: raise Http404 if not consent_provided and active_provider_enforces_data_sharing( request, EnterpriseCustomer.AT_LOGIN): # Flush the session to avoid the possibility of accidental login and to abort the pipeline. # pipeline is flushed only if data sharing is enforced, in other cases let the user to login. request.session.flush() return redirect(reverse('dashboard')) ec_user, __ = EnterpriseCustomerUser.objects.get_or_create( user_id=user.id, enterprise_customer=customer, ) UserDataSharingConsentAudit.objects.update_or_create( user=ec_user, defaults={ 'state': (UserDataSharingConsentAudit.ENABLED if consent_provided else UserDataSharingConsentAudit.DISABLED) }) # Resume auth pipeline backend_name = request.session.get('partial_pipeline', {}).get('backend') return redirect(get_complete_url(backend_name))
def get_account_consent(self, request): """ Render a form to collect consent for account-wide data sharing. This method is called when no course ID is passed as a URL parameter; a form will be rendered with messaging around the concept of granting consent for the entire platform. """ # Get the OpenEdX platform name platform_name = configuration_helpers.get_value( "PLATFORM_NAME", settings.PLATFORM_NAME) # Get the EnterpriseCustomer for the request; raise an error if there isn't one. customer = get_enterprise_customer_for_request(request) if customer is None: raise Http404 # Quarantine the user to this module. self.quarantine(request) required = customer.enforces_data_sharing_consent( EnterpriseCustomer.AT_LOGIN) data = { 'platform_name': platform_name, 'enterprise_customer_name': customer.name, 'data_sharing_consent': 'required' if required else 'optional', "messages": { "warning": self.get_warning(customer.name, platform_name, required), "note": self.get_note(customer.name, required), }, "course_id": None, "course_specific": False, 'enrollment_deferred': False, } return render_to_response('grant_data_sharing_permissions.html', data, request=request)
def post(self, request): """ Process the above form. """ # Verify that all necessary resources are present verify_edx_resources() self.lift_quarantine(request) customer = get_enterprise_customer_for_request(request) if customer is None: raise Http404 consent_provided = request.POST.get('data_sharing_consent', False) # If the checkbox is unchecked, no value will be sent user = get_real_social_auth_object(request).user ec_user, __ = EnterpriseCustomerUser.objects.get_or_create( user_id=user.id, enterprise_customer=customer, ) UserDataSharingConsentAudit.objects.update_or_create( user=ec_user, defaults={ 'state': (UserDataSharingConsentAudit.ENABLED if consent_provided else UserDataSharingConsentAudit.DISABLED) }) if not consent_provided: # Flush the session to avoid the possibility of accidental login and to abort the pipeline. # pipeline is flushed only if data sharing is enforced, in other cases let the user to login. if active_provider_enforces_data_sharing( request, EnterpriseCustomer.AT_LOGIN): request.session.flush() return redirect(reverse('dashboard')) # Resume auth pipeline backend_name = request.session.get('partial_pipeline', {}).get('backend') return redirect(get_complete_url(backend_name))
def enterprise_customer_for_request(request, tpa_hint=None): """ Check all the context clues of the request to determine if the request being made is tied to a particular EnterpriseCustomer. """ if not enterprise_enabled(): return None ec = get_enterprise_customer_for_request(request) if not ec and tpa_hint: try: ec = EnterpriseCustomer.objects.get(enterprise_customer_identity_provider__provider_id=tpa_hint) except EnterpriseCustomer.DoesNotExist: pass ec_uuid = request.GET.get('enterprise_customer') or request.COOKIES.get(settings.ENTERPRISE_CUSTOMER_COOKIE_NAME) if not ec and ec_uuid: try: ec = EnterpriseCustomer.objects.get(uuid=ec_uuid) except (EnterpriseCustomer.DoesNotExist, ValueError): ec = None return ec
def post_account_consent(self, request, consent_provided): """ Interpret the account-wide form above, and save it to a UserDataSharingConsentAudit object for later retrieval. """ self.lift_quarantine(request) # Load the linked EnterpriseCustomer for this request. customer = get_enterprise_customer_for_request(request) if customer is None: # If we can't get an EnterpriseCustomer from the pipeline, then we don't really # have enough state to do anything meaningful. Just send the user to the login # screen; if they want to sign in with an Enterprise-linked SSO, they can do # so, and the pipeline will get them back here if they need to be. return redirect('signin_user') # Attempt to retrieve a user being manipulated by the third-party auth # pipeline. Return a 404 if no such user exists. social_auth = get_real_social_auth_object(request) user = getattr(social_auth, 'user', None) if user is None: raise Http404 if not consent_provided and active_provider_enforces_data_sharing( request, EnterpriseCustomer.AT_LOGIN): # Flush the session to avoid the possibility of accidental login and to abort the pipeline. # pipeline is flushed only if data sharing is enforced, in other cases let the user to login. request.session.flush() failure_url = request.POST.get('failure_url') or reverse( 'dashboard') return redirect(failure_url) enterprise_customer_user, __ = EnterpriseCustomerUser.objects.get_or_create( user_id=user.id, enterprise_customer=customer, ) platform_name = configuration_helpers.get_value( 'PLATFORM_NAME', settings.PLATFORM_NAME) messages.success( request, _('{span_start}Account created{span_end} Thank you for creating an account with {platform_name}.' ).format( platform_name=platform_name, span_start='<span>', span_end='</span>', )) if not user.is_active: messages.info( request, _('{span_start}Activate your account{span_end} Check your inbox for an activation email. ' 'You will not be able to log back into your account until you have activated it.' ).format(span_start='<span>', span_end='</span>')) UserDataSharingConsentAudit.objects.update_or_create( user=enterprise_customer_user, defaults={ 'state': (UserDataSharingConsentAudit.ENABLED if consent_provided else UserDataSharingConsentAudit.DISABLED) }) # Resume auth pipeline backend_name = get_partial_pipeline(request).get('backend') return redirect(get_complete_url(backend_name))
def get_account_consent(self, request): """ Render a form to collect consent for account-wide data sharing. This method is called when no course ID is passed as a URL parameter; a form will be rendered with messaging around the concept of granting consent for the entire platform. """ # Get the OpenEdX platform name platform_name = configuration_helpers.get_value( "PLATFORM_NAME", settings.PLATFORM_NAME) # Get the EnterpriseCustomer for the request. customer = get_enterprise_customer_for_request(request) if customer is None: # If we can't get an EnterpriseCustomer from the pipeline, then we don't really # have enough state to do anything meaningful. Just send the user to the login # screen; if they want to sign in with an Enterprise-linked SSO, they can do # so, and the pipeline will get them back here if they need to be. return redirect('signin_user') # Quarantine the user to this module. self.quarantine(request) failure_url = request.GET.get('failure_url') context_data = self.get_default_context(customer, platform_name) account_specific_context = { 'consent_request_prompt': CONSENT_REQUEST_PROMPT.format( # pylint: disable=no-member enterprise_customer_name=customer.name), 'confirmation_alert_prompt': CONFIRMATION_ALERT_PROMPT.format( # pylint: disable=no-member enterprise_customer_name=customer.name), 'confirmation_alert_prompt_warning': CONFIRMATION_ALERT_PROMPT_WARNING.format( # pylint: disable=no-member enterprise_customer_name=customer.name, ), 'LANGUAGE_CODE': get_language_from_request(request), 'platform_name': platform_name, 'enterprise_customer_name': customer.name, "course_id": None, "course_specific": False, 'enrollment_deferred': False, 'failure_url': failure_url, 'requested_permissions': [ _('your enrollment in all sponsored courses'), _('your learning progress'), _('course completion'), ], 'enterprise_customer': customer, 'enterprise_welcome_text': self.enterprise_welcome_text.format( enterprise_customer_name=customer.name, platform_name=platform_name, strong_start='<strong>', strong_end='</strong>', ), } context_data.update(account_specific_context) return render(request, 'enterprise/grant_data_sharing_permissions.html', context=context_data)