Example #1
0
    def test_login_url_raises_value_error_if_provider_not_enabled(self):
        provider_id = 'oa2-not-enabled'

        assert provider.Registry.get(provider_id) is None

        with pytest.raises(ValueError):
            pipeline.get_login_url(provider_id, pipeline.AUTH_ENTRY_LOGIN)
Example #2
0
    def test_for_value_error_if_provider_id_invalid(self):
        provider_id = 'invalid'  # Format is normally "{prefix}-{identifier}"

        with pytest.raises(ValueError):
            provider.Registry.get(provider_id)

        with pytest.raises(ValueError):
            pipeline.get_login_url(provider_id, pipeline.AUTH_ENTRY_LOGIN)

        with pytest.raises(ValueError):
            pipeline.get_disconnect_url(provider_id, 1000)

        with pytest.raises(ValueError):
            pipeline.get_complete_url(provider_id)
Example #3
0
    def get(self, request, *args, **kwargs):  # lint-amnesty, pylint: disable=unused-argument
        """
        Return either a redirect to the login page of an identity provider that
        corresponds to the provider_slug keyword argument or a 404 if the
        provider_slug does not correspond to an identity provider.

        Args:
            request (HttpRequest)

        Keyword Args:
            provider_slug (str): a slug corresponding to a configured identity provider

        Returns:
            HttpResponse: 302 to a provider's login url if the provider_slug kwarg matches an identity provider
            HttpResponse: 404 if the provider_slug kwarg does not match an identity provider
        """
        # this gets the url to redirect to after login/registration/third_party_auth
        # it also handles checking the safety of the redirect url (next query parameter)
        # it checks against settings.LOGIN_REDIRECT_WHITELIST, so be sure to add the url
        # to this setting
        next_destination_url = get_next_url_for_login_page(request)

        try:
            url = pipeline.get_login_url(kwargs['provider_slug'], pipeline.AUTH_ENTRY_LOGIN, next_destination_url)
            return redirect(url)
        except ValueError:
            return HttpResponseNotFound()
Example #4
0
    def test_login_url_returns_expected_format(self):
        login_url = pipeline.get_login_url(self.enabled_provider.provider_id,
                                           pipeline.AUTH_ENTRY_LOGIN)

        assert login_url.startswith('/auth/login')
        assert self.enabled_provider.backend_name in login_url
        assert login_url.endswith(pipeline.AUTH_ENTRY_LOGIN)
Example #5
0
    def test_custom_form_error(self):
        """
        Use the Google provider to test the custom login/register failure redirects.
        """
        # The pipeline starts by a user GETting /auth/login/google-oauth2/?auth_entry=custom1
        # Synthesize that request and check that it redirects to the correct
        # provider page.
        auth_entry = 'custom1'  # See definition in lms/envs/test.py
        login_url = pipeline.get_login_url(self.provider.provider_id,
                                           auth_entry)
        login_url += "&next=/misc/final-destination"
        self.assert_redirect_to_provider_looks_correct(
            self.client.get(login_url))

        def fake_auth_complete_error(_inst, *_args, **_kwargs):
            """ Mock the backend's auth_complete() method """
            raise AuthException("Mock login failed")

        # Next, the provider makes a request against /auth/complete/<provider>.
        complete_url = pipeline.get_complete_url(self.provider.backend_name)
        with patch.object(self.provider.backend_class, 'auth_complete',
                          fake_auth_complete_error):
            response = self.client.get(complete_url)
        # This should redirect to the custom error URL
        assert response.status_code == 302
        assert response['Location'] == '/misc/my-custom-sso-error-page'
Example #6
0
    def test_with_valid_provider_slug(self):
        endpoint_url = self.get_idp_redirect_url('saml-test')
        expected_url = pipeline.get_login_url('saml-test', pipeline.AUTH_ENTRY_LOGIN, reverse('dashboard'))

        response = self.client.get(endpoint_url)

        self.assertEqual(response.status_code, 302)
        self.assertEqual(response.url, expected_url)
Example #7
0
    def test_custom_form(self):
        """
        Use the Google provider to test the custom login/register form feature.
        """
        # The pipeline starts by a user GETting /auth/login/google-oauth2/?auth_entry=custom1
        # Synthesize that request and check that it redirects to the correct
        # provider page.
        auth_entry = 'custom1'  # See definition in lms/envs/test.py
        login_url = pipeline.get_login_url(self.provider.provider_id, auth_entry)
        login_url += "&next=/misc/final-destination"
        self.assert_redirect_to_provider_looks_correct(self.client.get(login_url))

        def fake_auth_complete(inst, *args, **kwargs):
            """ Mock the backend's auth_complete() method """
            kwargs.update({'response': self.get_response_data(), 'backend': inst})
            return inst.strategy.authenticate(*args, **kwargs)

        # Next, the provider makes a request against /auth/complete/<provider>.
        complete_url = pipeline.get_complete_url(self.provider.backend_name)
        with patch.object(self.provider.backend_class, 'auth_complete', fake_auth_complete):
            response = self.client.get(complete_url)
        # This should redirect to the custom login/register form:
        assert response.status_code == 302
        assert response['Location'] == '/auth/custom_auth_entry'

        response = self.client.get(response['Location'])
        assert response.status_code == 200
        assert 'action="/misc/my-custom-registration-form" method="post"' in response.content.decode('utf-8')
        data_decoded = base64.b64decode(response.context['data']).decode('utf-8')
        data_parsed = json.loads(data_decoded)
        # The user's details get passed to the custom page as a base64 encoded query parameter:
        assert data_parsed == {'auth_entry': 'custom1', 'backend_name': 'google-oauth2',
                               'provider_id': 'oa2-google-oauth2',
                               'user_details': {'username': '******', 'email': '*****@*****.**',
                                                'fullname': 'name_value', 'first_name': 'given_name_value',
                                                'last_name': 'family_name_value'}}
        # Check the hash that is used to confirm the user's data in the GET parameter is correct
        secret_key = settings.THIRD_PARTY_AUTH_CUSTOM_AUTH_FORMS['custom1']['secret_key']
        hmac_expected = hmac.new(
            secret_key.encode('utf-8'),
            msg=data_decoded.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        assert base64.b64decode(response.context['hmac']) == hmac_expected

        # Now our custom registration form creates or logs in the user:
        email, password = data_parsed['user_details']['email'], 'random_password'
        created_user = UserFactory(email=email, password=password)
        login_response = self.client.post(reverse('login_api'), {'email': email, 'password': password})
        assert login_response.status_code == 200

        # Now our custom login/registration page must resume the pipeline:
        response = self.client.get(complete_url)
        assert response.status_code == 302
        assert response['Location'] == '/misc/final-destination'

        _, strategy = self.get_request_and_strategy()
        self.assert_social_auth_exists_for_user(created_user, strategy)
Example #8
0
    def get(self, request):
        """
        GET /api/third_party_auth/v0/providers/user_status/

        **GET Response Values**
        ```
        {
            "accepts_logins": true,
            "name": "Google",
            "disconnect_url": "/auth/disconnect/google-oauth2/?",
            "connect_url": "/auth/login/google-oauth2/?auth_entry=account_settings&next=%2Faccount%2Fsettings",
            "connected": false,
            "id": "oa2-google-oauth2"
        }
        ```
        """
        tpa_states = []
        for state in pipeline.get_provider_user_states(request.user):
            # We only want to include providers if they are either currently available to be logged
            # in with, or if the user is already authenticated with them.
            if state.provider.display_for_login or state.has_account:
                tpa_states.append({
                    'id':
                    state.provider.provider_id,
                    'name':
                    state.provider.
                    name,  # The name of the provider e.g. Facebook
                    'connected':
                    state.
                    has_account,  # Whether the user's edX account is connected with the provider.
                    # If the user is not connected, they should be directed to this page to authenticate
                    # with the particular provider, as long as the provider supports initiating a login.
                    'connect_url':
                    pipeline.get_login_url(
                        state.provider.provider_id,
                        pipeline.AUTH_ENTRY_ACCOUNT_SETTINGS,
                        # The url the user should be directed to after the auth process has completed.
                        redirect_url=reverse('account_settings'),
                    ),
                    'accepts_logins':
                    state.provider.accepts_logins,
                    # If the user is connected, sending a POST request to this url removes the connection
                    # information for this provider from their edX account.
                    'disconnect_url':
                    pipeline.get_disconnect_url(state.provider.provider_id,
                                                state.association_id),
                })

        return Response(tpa_states)
Example #9
0
    def get_social_oauth_providers_data(self):
        """
        Get the context data related to the social auth providers

        Returns:
            dict: A dictionary containing the data for social oauth SSO providers
        """
        context = {'providers': [], 'is_any_social_auth_connected': False}

        if third_party_auth.is_enabled():
            auth_states = pipeline.get_provider_user_states(self.request.user)

            for state in auth_states:
                if state.provider.display_for_login or state.has_account:
                    if state.has_account:
                        context['is_any_social_auth_connected'] = True

                    context['providers'].append({
                        'id':
                        state.provider.provider_id,
                        'name':
                        state.provider.name,
                        'connected':
                        state.has_account,
                        'accepts_logins':
                        state.provider.accepts_logins,
                        'disconnect_url':
                        pipeline.get_disconnect_url(state.provider.provider_id,
                                                    state.association_id),
                        'connect_url':
                        pipeline.get_login_url(
                            state.provider.provider_id,
                            pipeline.AUTH_ENTRY_ACCOUNT_SETTINGS,
                            redirect_url=reverse('adg_account_settings'),
                        ),
                        **ACCOUNT_INFO.get(state.provider.provider_id)
                    })

        return context
Example #10
0
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: We check for the existence of login-related cookies in addition to is_authenticated
    #  since Django's SessionAuthentication middleware auto-updates session cookies but not
    #  the other login-related cookies. See ARCH-282.
    if request.user.is_authenticated and are_logged_in_cookies_set(request):
        return redirect(redirect_to)

    # 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 = six.moves.urllib.parse.parse_qs(six.moves.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(u"Unknown tpa_hint provider: %s", ex)

    enterprise_customer = enterprise_customer_for_request(request)
    # Redirect to authn MFE if it is enabled
    if should_redirect_to_authn_microfrontend() and not enterprise_customer:

        # 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_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
Example #11
0
def third_party_auth_context(request, redirect_to, tpa_hint=None):
    """
    Context for third party auth providers and the currently running pipeline.

    Arguments:
        request (HttpRequest): The request, used to determine if a pipeline
            is currently running.
        redirect_to: The URL to send the user to following successful
            authentication.
        tpa_hint (string): An override flag that will return a matching provider
            as long as its configuration has been enabled

    Returns:
        dict

    """
    context = {
        "currentProvider":
        None,
        "platformName":
        configuration_helpers.get_value('PLATFORM_NAME',
                                        settings.PLATFORM_NAME),
        "providers": [],
        "secondaryProviders": [],
        "finishAuthUrl":
        None,
        "errorMessage":
        None,
        "registerFormSubmitButtonText":
        _("Create Account"),
        "syncLearnerProfileData":
        False,
        "pipeline_user_details": {}
    }

    if third_party_auth.is_enabled():
        for enabled in third_party_auth.provider.Registry.displayed_for_login(
                tpa_hint=tpa_hint):
            info = {
                "id":
                enabled.provider_id,
                "name":
                enabled.name,
                "iconClass":
                enabled.icon_class or None,
                "iconImage":
                enabled.icon_image.url if enabled.icon_image else None,
                "skipHintedLogin":
                enabled.skip_hinted_login_dialog,
                "loginUrl":
                pipeline.get_login_url(
                    enabled.provider_id,
                    pipeline.AUTH_ENTRY_LOGIN,
                    redirect_url=redirect_to,
                ),
                "registerUrl":
                pipeline.get_login_url(
                    enabled.provider_id,
                    pipeline.AUTH_ENTRY_REGISTER,
                    redirect_url=redirect_to,
                ),
            }
            context["providers" if not enabled.
                    secondary else "secondaryProviders"].append(info)

        running_pipeline = pipeline.get(request)
        if running_pipeline is not None:
            current_provider = third_party_auth.provider.Registry.get_from_pipeline(
                running_pipeline)
            user_details = running_pipeline['kwargs']['details']
            if user_details:
                username = running_pipeline['kwargs'].get(
                    'username') or user_details.get('username')
                if username:
                    user_details['username'] = clean_username(username)
                context['pipeline_user_details'] = user_details

            if current_provider is not None:
                context["currentProvider"] = current_provider.name
                context["finishAuthUrl"] = pipeline.get_complete_url(
                    current_provider.backend_name)
                context[
                    "syncLearnerProfileData"] = current_provider.sync_learner_profile_data

                if current_provider.skip_registration_form:
                    # As a reliable way of "skipping" the registration form, we just submit it automatically
                    context["autoSubmitRegForm"] = True

        # Check for any error messages we may want to display:
        for msg in messages.get_messages(request):
            if msg.extra_tags.split()[0] == "social-auth":
                # msg may or may not be translated. Try translating [again] in case we are able to:
                context["errorMessage"] = _(str(msg))  # pylint: disable=E7610
                break

    return context
Example #12
0
def account_settings_context(request):
    """ Context for the account settings page.

    Args:
        request: The request object.

    Returns:
        dict

    """
    user = request.user

    year_of_birth_options = [(six.text_type(year), six.text_type(year))
                             for year in UserProfile.VALID_YEARS]
    try:
        user_orders = get_user_orders(user)
    except:  # pylint: disable=bare-except
        log.exception('Error fetching order history from Otto.')
        # Return empty order list as account settings page expect a list and
        # it will be broken if exception raised
        user_orders = []

    beta_language = {}
    dark_lang_config = DarkLangConfig.current()
    if dark_lang_config.enable_beta_languages:
        user_preferences = get_user_preferences(user)
        pref_language = user_preferences.get('pref-lang')
        if pref_language in dark_lang_config.beta_languages_list:
            beta_language['code'] = pref_language
            beta_language['name'] = settings.LANGUAGE_DICT.get(pref_language)

    context = {
        'auth': {},
        'duplicate_provider':
        None,
        'nav_hidden':
        True,
        'fields': {
            'country': {
                'options': list(countries),
            },
            'gender': {
                'options': [(choice[0], _(choice[1]))
                            for choice in UserProfile.GENDER_CHOICES],
            },
            'language': {
                'options': released_languages(),
            },
            'level_of_education': {
                'options':
                [(choice[0], _(choice[1]))
                 for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES],
            },
            'password': {
                'url': reverse('password_reset'),
            },
            'year_of_birth': {
                'options': year_of_birth_options,
            },
            'preferred_language': {
                'options': all_languages(),
            },
            'time_zone': {
                'options': TIME_ZONE_CHOICES,
            }
        },
        'platform_name':
        configuration_helpers.get_value('PLATFORM_NAME',
                                        settings.PLATFORM_NAME),
        'password_reset_support_link':
        configuration_helpers.get_value('PASSWORD_RESET_SUPPORT_LINK',
                                        settings.PASSWORD_RESET_SUPPORT_LINK)
        or settings.SUPPORT_SITE_LINK,
        'user_accounts_api_url':
        reverse("accounts_api", kwargs={'username': user.username}),
        'user_preferences_api_url':
        reverse('preferences_api', kwargs={'username': user.username}),
        'disable_courseware_js':
        True,
        'show_program_listing':
        ProgramsApiConfig.is_enabled(),
        'show_dashboard_tabs':
        True,
        'order_history':
        user_orders,
        'disable_order_history_tab':
        should_redirect_to_order_history_microfrontend(),
        'enable_account_deletion':
        configuration_helpers.get_value(
            'ENABLE_ACCOUNT_DELETION',
            settings.FEATURES.get('ENABLE_ACCOUNT_DELETION', False)),
        'extended_profile_fields':
        _get_extended_profile_fields(),
        'beta_language':
        beta_language,
    }

    enterprise_customer = enterprise_customer_for_request(request)
    update_account_settings_context_for_enterprise(context,
                                                   enterprise_customer, user)

    if third_party_auth.is_enabled():
        # If the account on the third party provider is already connected with another edX account,
        # we display a message to the user.
        context['duplicate_provider'] = pipeline.get_duplicate_provider(
            messages.get_messages(request))

        auth_states = pipeline.get_provider_user_states(user)

        context['auth']['providers'] = [
            {
                'id':
                state.provider.provider_id,
                'name':
                state.provider.name,  # The name of the provider e.g. Facebook
                'connected':
                state.
                has_account,  # Whether the user's edX account is connected with the provider.
                # If the user is not connected, they should be directed to this page to authenticate
                # with the particular provider, as long as the provider supports initiating a login.
                'connect_url':
                pipeline.get_login_url(
                    state.provider.provider_id,
                    pipeline.AUTH_ENTRY_ACCOUNT_SETTINGS,
                    # The url the user should be directed to after the auth process has completed.
                    redirect_url=reverse('account_settings'),
                ),
                'accepts_logins':
                state.provider.accepts_logins,
                # If the user is connected, sending a POST request to this url removes the connection
                # information for this provider from their edX account.
                'disconnect_url':
                pipeline.get_disconnect_url(state.provider.provider_id,
                                            state.association_id),
                # We only want to include providers if they are either currently available to be logged
                # in with, or if the user is already authenticated with them.
            } for state in auth_states
            if state.provider.display_for_login or state.has_account
        ]

    return context
    def test_full_pipeline_succeeds_for_unlinking_testshib_account(
        self,
        mock_enterprise_customer_for_request_settings_view,
        mock_enterprise_customer_for_request,
    ):

        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        self.provider = self._configure_testshib_provider()
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN,
            redirect_uri='social:complete')
        request.backend.auth_complete = MagicMock(
            return_value=self.fake_auth_complete(strategy))
        user = self.create_user_models_for_existing_account(
            strategy, '*****@*****.**', 'password', self.get_username())
        self.assert_social_auth_exists_for_user(user, strategy)

        request.user = user

        # We're already logged in, so simulate that the cookie is set correctly
        self.set_logged_in_cookies(request)

        # linking a learner with enterprise customer.
        enterprise_customer = EnterpriseCustomerFactory()
        assert EnterpriseCustomerUser.objects.count(
        ) == 0, "Precondition check: no link records should exist"
        EnterpriseCustomerUser.objects.link_user(enterprise_customer,
                                                 user.email)
        assert (EnterpriseCustomerUser.objects.filter(
            enterprise_customer=enterprise_customer,
            user_id=user.id).count() == 1)
        EnterpriseCustomerIdentityProvider.objects.get_or_create(
            enterprise_customer=enterprise_customer,
            provider_id=self.provider.provider_id)

        enterprise_customer_data = {
            'uuid': enterprise_customer.uuid,
            'name': enterprise_customer.name,
            'identity_provider': 'saml-default',
        }
        mock_enterprise_customer_for_request.return_value = enterprise_customer_data
        mock_enterprise_customer_for_request_settings_view.return_value = enterprise_customer_data

        # Instrument the pipeline to get to the dashboard with the full expected state.
        self.client.get(
            pipeline.get_login_url(self.provider.provider_id,
                                   pipeline.AUTH_ENTRY_LOGIN))

        actions.do_complete(
            request.backend,
            social_views._do_login,  # pylint: disable=protected-access
            request=request)

        with self._patch_edxmako_current_request(strategy.request):
            login_user(strategy.request)
            actions.do_complete(
                request.backend,
                social_views._do_login,
                user=user,  # pylint: disable=protected-access
                request=request)

        # First we expect that we're in the linked state, with a backend entry.
        self.assert_account_settings_context_looks_correct(
            account_settings_context(request), linked=True)
        self.assert_social_auth_exists_for_user(request.user, strategy)

        FEATURES_WITH_ENTERPRISE_ENABLED = settings.FEATURES.copy()
        FEATURES_WITH_ENTERPRISE_ENABLED[
            'ENABLE_ENTERPRISE_INTEGRATION'] = True
        with patch.dict("django.conf.settings.FEATURES",
                        FEATURES_WITH_ENTERPRISE_ENABLED):
            # Fire off the disconnect pipeline without the user information.
            actions.do_disconnect(request.backend,
                                  None,
                                  None,
                                  redirect_field_name=auth.REDIRECT_FIELD_NAME,
                                  request=request)
            assert EnterpriseCustomerUser.objects\
                .filter(enterprise_customer=enterprise_customer, user_id=user.id).count() != 0

            # Fire off the disconnect pipeline to unlink.
            self.assert_redirect_after_pipeline_completes(
                actions.do_disconnect(
                    request.backend,
                    user,
                    None,
                    redirect_field_name=auth.REDIRECT_FIELD_NAME,
                    request=request))
            # Now we expect to be in the unlinked state, with no backend entry.
            self.assert_account_settings_context_looks_correct(
                account_settings_context(request), linked=False)
            self.assert_social_auth_does_not_exist_for_user(user, strategy)
            assert EnterpriseCustomerUser.objects\
                .filter(enterprise_customer=enterprise_customer, user_id=user.id).count() == 0