def test_login_url_raises_value_error_if_provider_not_enabled(self):
        provider_id = 'oa2-not-enabled'

        self.assertIsNone(provider.Registry.get(provider_id))

        with self.assertRaises(ValueError):
            pipeline.get_login_url(provider_id, pipeline.AUTH_ENTRY_LOGIN)
Beispiel #2
0
def _third_party_auth_context(request, redirect_to):
    """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.

    Returns:
        dict

    """
    context = {
        "currentProvider": None,
        "providers": [],
        "secondaryProviders": [],
        "finishAuthUrl": None,
        "errorMessage": None,
    }

    if third_party_auth.is_enabled():
        for enabled in third_party_auth.provider.Registry.accepting_logins():
            info = {
                "id": enabled.provider_id,
                "name": enabled.name,
                "iconClass": enabled.icon_class,
                "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)

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

                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'] = _(unicode(msg))  # pylint: disable=translation-of-non-string
                break

    return context
Beispiel #3
0
def _third_party_auth_context(request, redirect_to):
    """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.

    Returns:
        dict

    """
    context = {
        "currentProvider": None,
        "providers": [],
        "secondaryProviders": [],
        "finishAuthUrl": None,
        "errorMessage": None,
    }

    if third_party_auth.is_enabled():
        for enabled in third_party_auth.provider.Registry.accepting_logins():
            info = {
                "id": enabled.provider_id,
                "name": enabled.name,
                "iconClass": enabled.icon_class,
                "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)

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

                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'] = _(unicode(msg))  # pylint: disable=translation-of-non-string
                break

    return context
    def test_login_url_raises_value_error_if_provider_not_enabled(self):
        provider_id = 'oa2-not-enabled'

        self.assertIsNone(provider.Registry.get(provider_id))

        with self.assertRaises(ValueError):
            pipeline.get_login_url(provider_id, pipeline.AUTH_ENTRY_LOGIN)
Beispiel #5
0
def _third_party_auth_context(request, redirect_to):
    """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.

    Returns:
        dict

    """
    context = {
        "currentProvider": None,
        "providers": [],
        "finishAuthUrl": None,
        "errorMessage": None,
    }

    if third_party_auth.is_enabled():
        context["providers"] = [
            {
                "name": enabled.NAME,
                "iconClass": enabled.ICON_CLASS,
                "loginUrl": pipeline.get_login_url(
                    enabled.NAME,
                    pipeline.AUTH_ENTRY_LOGIN,
                    redirect_url=redirect_to,
                ),
                "registerUrl": pipeline.get_login_url(
                    enabled.NAME,
                    pipeline.AUTH_ENTRY_REGISTER,
                    redirect_url=redirect_to,
                ),
            }
            for enabled in third_party_auth.provider.Registry.enabled()
        ]

        running_pipeline = pipeline.get(request)
        if running_pipeline is not None:
            current_provider = third_party_auth.provider.Registry.get_by_backend_name(
                running_pipeline.get('backend')
            )
            context["currentProvider"] = current_provider.NAME
            context["finishAuthUrl"] = pipeline.get_complete_url(current_provider.BACKEND_CLASS.name)

        # 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":
                context['errorMessage'] = unicode(msg)
                break

    return context
    def test_for_value_error_if_provider_id_invalid(self):
        provider_id = 'invalid'  # Format is normally "{prefix}-{identifier}"

        with self.assertRaises(ValueError):
            provider.Registry.get(provider_id)

        with self.assertRaises(ValueError):
            pipeline.get_login_url(provider_id, pipeline.AUTH_ENTRY_LOGIN)

        with self.assertRaises(ValueError):
            pipeline.get_disconnect_url(provider_id, 1000)

        with self.assertRaises(ValueError):
            pipeline.get_complete_url(provider_id)
    def test_for_value_error_if_provider_id_invalid(self):
        provider_id = 'invalid'  # Format is normally "{prefix}-{identifier}"

        with self.assertRaises(ValueError):
            provider.Registry.get(provider_id)

        with self.assertRaises(ValueError):
            pipeline.get_login_url(provider_id, pipeline.AUTH_ENTRY_LOGIN)

        with self.assertRaises(ValueError):
            pipeline.get_disconnect_url(provider_id, 1000)

        with self.assertRaises(ValueError):
            pipeline.get_complete_url(provider_id)
Beispiel #8
0
    def test_already_associated_exception_populates_dashboard_with_error(self):
        # Instrument the pipeline with an exception. We test that the
        # exception is raised correctly separately, so it's ok that we're
        # raising it artificially here. This makes the linked=True artificial
        # in the final assert because in practice the account would be
        # unlinked, but getting that behavior is cumbersome here and already
        # covered in other tests. Using linked=True does, however, let us test
        # that the duplicate error has no effect on the state of the controls.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
        strategy.request.backend.auth_complete = mock.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)

        self.client.get('/login')
        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):
            signin_user(strategy.request)
            login_user(strategy.request)
            actions.do_complete(request.backend, social_views._do_login,  # pylint: disable=protected-access
                                user=user, request=request)

        # Monkey-patch storage for messaging; pylint: disable=protected-access
        request._messages = fallback.FallbackStorage(request)
        middleware.ExceptionMiddleware().process_exception(
            request,
            exceptions.AuthAlreadyAssociated(self.provider.backend_name, 'account is already in use.'))

        self.assert_account_settings_context_looks_correct(
            account_settings_context(request), duplicate=True, linked=True)
Beispiel #9
0
    def test_full_pipeline_succeeds_for_unlinking_account(self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
        request.backend.auth_complete = mock.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)

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

        # 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

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

        # 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), user, linked=True)
        self.assert_social_auth_exists_for_user(request.user, strategy)

        # Fire off the disconnect pipeline to unlink.
        self.assert_redirect_to_dashboard_looks_correct(actions.do_disconnect(
            request.backend, request.user, None, redirect_field_name=auth.REDIRECT_FIELD_NAME))

        # Now we expect to be in the unlinked state, with no backend entry.
        self.assert_account_settings_context_looks_correct(account_settings_context(request), user, linked=False)
        self.assert_social_auth_does_not_exist_for_user(user, strategy)
Beispiel #10
0
    def test_already_associated_exception_populates_dashboard_with_error(self):
        # Instrument the pipeline with an exception. We test that the
        # exception is raised correctly separately, so it's ok that we're
        # raising it artificially here. This makes the linked=True artificial
        # in the final assert because in practice the account would be
        # unlinked, but getting that behavior is cumbersome here and already
        # covered in other tests. Using linked=True does, however, let us test
        # that the duplicate error has no effect on the state of the controls.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
        strategy.request.backend.auth_complete = mock.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)

        self.client.get('/login')
        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):
            signin_user(strategy.request)
            login_user(strategy.request)
            actions.do_complete(request.backend, social_views._do_login,  # pylint: disable=protected-access
                                user=user, request=request)

        # Monkey-patch storage for messaging; pylint: disable=protected-access
        request._messages = fallback.FallbackStorage(request)
        middleware.ExceptionMiddleware().process_exception(
            request,
            exceptions.AuthAlreadyAssociated(self.provider.backend_name, 'account is already in use.'))

        self.assert_account_settings_context_looks_correct(
            account_settings_context(request), duplicate=True, linked=True)
Beispiel #11
0
    def test_full_pipeline_succeeds_for_unlinking_account(self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
        strategy.backend.auth_complete = mock.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)

        # Instrument the pipeline to get to the dashboard with the full
        # expected state.
        self.client.get(
            pipeline.get_login_url(self.PROVIDER_CLASS.NAME, pipeline.AUTH_ENTRY_LOGIN))
        actions.do_complete(strategy, social_views._do_login)  # pylint: disable-msg=protected-access

        mako_middleware_process_request(strategy.request)
        student_views.signin_user(strategy.request)
        student_views.login_user(strategy.request)
        actions.do_complete(strategy, social_views._do_login, user=user)  # pylint: disable-msg=protected-access

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

        # Fire off the disconnect pipeline to unlink.
        self.assert_redirect_to_dashboard_looks_correct(actions.do_disconnect(
            request.social_strategy, request.user, None, redirect_field_name=auth.REDIRECT_FIELD_NAME))

        # Now we expect to be in the unlinked state, with no backend entry.
        self.assert_dashboard_response_looks_correct(student_views.dashboard(request), user, linked=False)
        self.assert_social_auth_does_not_exist_for_user(user, strategy)
Beispiel #12
0
def auth_pipeline_urls(auth_entry, redirect_url=None, course_id=None):
    """Retrieve URLs for each enabled third-party auth provider.

    These URLs are used on the "sign up" and "sign in" buttons
    on the login/registration forms to allow users to begin
    authentication with a third-party provider.

    Optionally, we can redirect the user to an arbitrary
    url after auth completes successfully.  We use this
    to redirect the user to a page that required login,
    or to send users to the payment flow when enrolling
    in a course.

    Args:
        auth_entry (string): Either `pipeline.AUTH_ENTRY_LOGIN` or `pipeline.AUTH_ENTRY_REGISTER`

    Keyword Args:
        redirect_url (unicode): If provided, send users to this URL
            after they successfully authenticate.

        course_id (unicode): The ID of the course the user is enrolling in.
            We use this to send users to the track selection page
            if the course has a payment option.
            Note that `redirect_url` takes precedence over the redirect
            to the track selection page.

    Returns:
        dict mapping provider names to URLs

    """
    if not third_party_auth_enabled():
        return {}

    if redirect_url is not None:
        pipeline_redirect = redirect_url
    elif course_id is not None:
        # If the course is white-label (paid), then we send users
        # to the shopping cart.  (There is a third party auth pipeline
        # step that will add the course to the cart.)
        if CourseMode.is_white_label(CourseKey.from_string(course_id)):
            pipeline_redirect = reverse("shoppingcart.views.show_cart")

        # Otherwise, send the user to the track selection page.
        # The track selection page may redirect the user to the dashboard
        # (if the only available mode is honor), or directly to verification
        # (for professional ed).
        else:
            pipeline_redirect = reverse(
                "course_modes_choose",
                kwargs={'course_id': unicode(course_id)})
    else:
        pipeline_redirect = None

    return {
        provider.NAME: pipeline.get_login_url(provider.NAME,
                                              auth_entry,
                                              enroll_course_id=course_id,
                                              redirect_url=pipeline_redirect)
        for provider in provider.Registry.enabled()
    }
Beispiel #13
0
    def test_full_pipeline_succeeds_for_linking_account(self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN,
            redirect_uri='social:complete')
        request.backend.auth_complete = mock.MagicMock(
            return_value=self.fake_auth_complete(strategy))
        pipeline.analytics.track = mock.MagicMock()
        request.user = self.create_user_models_for_existing_account(
            strategy,
            '*****@*****.**',
            'password',
            self.get_username(),
            skip_social_auth=True)

        # 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

        mako_middleware_process_request(strategy.request)
        student_views.signin_user(strategy.request)
        student_views.login_user(strategy.request)
        actions.do_complete(request.backend, social_views._do_login)  # pylint: disable=protected-access

        # First we expect that we're in the unlinked state, and that there
        # really is no association in the backend.
        self.assert_account_settings_context_looks_correct(
            account_settings_context(request), request.user, linked=False)
        self.assert_social_auth_does_not_exist_for_user(request.user, strategy)

        # We should be redirected back to the complete page, setting
        # the "logged in" cookie for the marketing site.
        self.assert_logged_in_cookie_redirect(
            actions.do_complete(
                request.backend,
                social_views._do_login,
                request.user,
                None,  # pylint: disable=protected-access
                redirect_field_name=auth.REDIRECT_FIELD_NAME))

        # Set the cookie and try again
        self.set_logged_in_cookies(request)

        # Fire off the auth pipeline to link.
        self.assert_redirect_to_dashboard_looks_correct(
            actions.do_complete(
                request.backend,
                social_views._do_login,
                request.user,
                None,  # pylint: disable=protected-access
                redirect_field_name=auth.REDIRECT_FIELD_NAME))

        # Now we expect to be in the linked state, with a backend entry.
        self.assert_social_auth_exists_for_user(request.user, strategy)
        self.assert_account_settings_context_looks_correct(
            account_settings_context(request), request.user, linked=True)
Beispiel #14
0
    def assert_dashboard_response_looks_correct(self, response, user, duplicate=False, linked=None):
        """Asserts the user's dashboard is in the expected state.

        We check unconditionally that the dashboard 200s and contains the
        user's info. If duplicate is True, we expect the duplicate account
        association error to be present. If linked is passed, we conditionally
        check the content and controls in the Account Links section of the
        sidebar.
        """
        duplicate_account_error_needle = '<section class="dashboard-banner third-party-auth">'
        assert_duplicate_presence_fn = self.assertIn if duplicate else self.assertNotIn

        self.assertEqual(200, response.status_code)
        self.assertIn(user.email, response.content)
        self.assertIn(user.username, response.content)
        assert_duplicate_presence_fn(duplicate_account_error_needle, response.content)

        if linked is not None:

            if linked:
                expected_control_text = pipeline.ProviderUserState(
                    self.PROVIDER_CLASS, user, False).get_unlink_form_name()
            else:
                expected_control_text = pipeline.get_login_url(self.PROVIDER_CLASS.NAME, pipeline.AUTH_ENTRY_DASHBOARD)

            icon_state = re.search(r'third-party-auth.+icon icon-(\w+)', response.content, re.DOTALL).groups()[0]
            provider_name = re.search(r'<span class="provider">([^<]+)', response.content, re.DOTALL).groups()[0]

            self.assertIn(expected_control_text, response.content)
            self.assertEqual('link' if linked else 'unlink', icon_state)
            self.assertEqual(self.PROVIDER_CLASS.NAME, provider_name)
    def test_login_url_returns_expected_format(self):
        login_url = pipeline.get_login_url(self.enabled_provider.provider_id,
                                           pipeline.AUTH_ENTRY_LOGIN)

        self.assertTrue(login_url.startswith('/auth/login'))
        self.assertIn(self.enabled_provider.backend_name, login_url)
        self.assertTrue(login_url.endswith(pipeline.AUTH_ENTRY_LOGIN))
Beispiel #16
0
    def get(self, request, *args, **kwargs):
        """
        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()
Beispiel #17
0
 def assert_signin_button_looks_functional(self, content, auth_entry):
     """Asserts JS is available to signin buttons and has the right args."""
     self.assertTrue(re.search(r'function thirdPartySignin', content))
     self.assertEqual(
         pipeline.get_login_url(self.PROVIDER_CLASS.NAME, auth_entry),
         re.search(r"thirdPartySignin\(event, '([^']+)",
                   content).groups()[0])
Beispiel #18
0
    def assert_dashboard_response_looks_correct(self, response, user, duplicate=False, linked=None):
        """Asserts the user's dashboard is in the expected state.

        We check unconditionally that the dashboard 200s and contains the
        user's info. If duplicate is True, we expect the duplicate account
        association error to be present. If linked is passed, we conditionally
        check the content and controls in the Account Links section of the
        sidebar.
        """
        duplicate_account_error_needle = '<section class="dashboard-banner third-party-auth">'
        assert_duplicate_presence_fn = self.assertIn if duplicate else self.assertNotIn

        self.assertEqual(200, response.status_code)
        self.assertIn(user.email, response.content.decode('UTF-8'))
        self.assertIn(user.username, response.content.decode('UTF-8'))
        assert_duplicate_presence_fn(duplicate_account_error_needle, response.content)

        if linked is not None:

            if linked:
                expected_control_text = pipeline.ProviderUserState(
                    self.PROVIDER_CLASS, user, False).get_unlink_form_name()
            else:
                expected_control_text = pipeline.get_login_url(self.PROVIDER_CLASS.NAME, pipeline.AUTH_ENTRY_DASHBOARD)

            icon_state = re.search(r'third-party-auth.+icon icon-(\w+)', response.content, re.DOTALL).groups()[0]
            provider_name = re.search(r'<span class="provider">([^<]+)', response.content, re.DOTALL).groups()[0]

            self.assertIn(expected_control_text, response.content)
            self.assertEqual('link' if linked else 'unlink', icon_state)
            self.assertEqual(self.PROVIDER_CLASS.NAME, provider_name)
Beispiel #19
0
    def test_full_pipeline_succeeds_for_unlinking_account(self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
        strategy.backend.auth_complete = mock.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)

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

        # Instrument the pipeline to get to the dashboard with the full
        # expected state.
        self.client.get(
            pipeline.get_login_url(self.PROVIDER_CLASS.NAME, pipeline.AUTH_ENTRY_LOGIN))
        actions.do_complete(strategy, social_views._do_login)  # pylint: disable-msg=protected-access

        mako_middleware_process_request(strategy.request)
        student_views.signin_user(strategy.request)
        student_views.login_user(strategy.request)
        actions.do_complete(strategy, social_views._do_login, user=user)  # pylint: disable-msg=protected-access

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

        # Fire off the disconnect pipeline to unlink.
        self.assert_redirect_to_dashboard_looks_correct(actions.do_disconnect(
            request.social_strategy, request.user, None, redirect_field_name=auth.REDIRECT_FIELD_NAME))

        # Now we expect to be in the unlinked state, with no backend entry.
        self.assert_dashboard_response_looks_correct(student_views.dashboard(request), user, linked=False)
        self.assert_social_auth_does_not_exist_for_user(user, strategy)
Beispiel #20
0
    def test_custom_form_links_by_email(self):
        self._configure_testshib_provider()
        self._freeze_time(timestamp=1434326820)  # This is the time when the saved request/response was recorded.

        email = '*****@*****.**'
        UserFactory(username='******', email=email, password='******')
        self._verify_user_email(email)
        self._assert_user_exists('myself', have_social=False)

        custom_url = pipeline.get_login_url('saml-testshib', 'custom2')
        self.client.get(custom_url)

        testshib_response = self._fake_testshib_login_and_return()
        # We should be redirected to TPA-complete endpoint
        self.assertEqual(testshib_response.status_code, 302)
        self.assertEqual(testshib_response['Location'], self.url_prefix + TPA_TESTSHIB_COMPLETE_URL)

        complete_response = self.client.get(testshib_response['Location'])
        # And we should be redirected to the dashboard
        self.assertEqual(complete_response.status_code, 302)
        self.assertEqual(complete_response['Location'], self.url_prefix + self.dashboard_page_url)

        # And account should now be linked to social
        self._assert_user_exists('myself', have_social=True)

        # Now check that we can login again:
        self.client.logout()
        self._test_return_login()
Beispiel #21
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
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'],
                         'http://example.none/misc/my-custom-sso-error-page')
Beispiel #22
0
    def test_full_pipeline_succeeds_for_signing_in_to_existing_active_account(self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
        )
        strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
        pipeline.analytics.track = mock.MagicMock()
        user = self.create_user_models_for_existing_account(
            strategy, "*****@*****.**", "password", self.get_username()
        )
        self.assert_social_auth_exists_for_user(user, strategy)
        self.assertTrue(user.is_active)

        # Begin! Ensure that the login form contains expected controls before
        # the user starts the pipeline.
        self.assert_login_response_before_pipeline_looks_correct(self.client.get("/login"))

        # The pipeline starts by a user GETting /auth/login/<provider>.
        # Synthesize that request and check that it redirects to the correct
        # provider page.
        self.assert_redirect_to_provider_looks_correct(
            self.client.get(pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN))
        )

        # Next, the provider makes a request against /auth/complete/<provider>
        # to resume the pipeline.
        # pylint: disable=protected-access
        self.assert_redirect_to_login_looks_correct(actions.do_complete(request.backend, social_views._do_login))

        # At this point we know the pipeline has resumed correctly. Next we
        # fire off the view that displays the login form and posts it via JS.
        with self._patch_edxmako_current_request(strategy.request):
            self.assert_login_response_in_pipeline_looks_correct(student_views.signin_user(strategy.request))

        # Next, we invoke the view that handles the POST, and expect it
        # redirects to /auth/complete. In the browser ajax handlers will
        # redirect the user to the dashboard; we invoke it manually here.
        self.assert_json_success_response_looks_correct(student_views.login_user(strategy.request))

        # We should be redirected back to the complete page, setting
        # the "logged in" cookie for the marketing site.
        self.assert_logged_in_cookie_redirect(
            actions.do_complete(
                request.backend,
                social_views._do_login,
                request.user,
                None,  # pylint: disable=protected-access
                redirect_field_name=auth.REDIRECT_FIELD_NAME,
            )
        )

        # Set the cookie and try again
        self.set_logged_in_cookies(request)

        self.assert_redirect_to_dashboard_looks_correct(
            actions.do_complete(request.backend, social_views._do_login, user=user)
        )
        self.assert_account_settings_context_looks_correct(account_settings_context(request), user)
Beispiel #23
0
    def test_full_pipeline_succeeds_registering_new_account(self):
        # First, create, the request and strategy that store pipeline state.
        # Mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_REGISTER, redirect_uri='social:complete')
        strategy.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))

        # Begin! Grab the registration page and check the login control on it.
        self.assert_register_response_before_pipeline_looks_correct(self.client.get('/register'))

        # The pipeline starts by a user GETting /auth/login/<provider>.
        # Synthesize that request and check that it redirects to the correct
        # provider page.
        self.assert_redirect_to_provider_looks_correct(self.client.get(
            pipeline.get_login_url(self.PROVIDER_CLASS.NAME, pipeline.AUTH_ENTRY_LOGIN)))

        # Next, the provider makes a request against /auth/complete/<provider>.
        # pylint:disable-msg=protected-access
        self.assert_redirect_to_register_looks_correct(actions.do_complete(strategy, social_views._do_login))

        mako_middleware_process_request(strategy.request)
        # At this point we know the pipeline has resumed correctly. Next we
        # fire off the view that displays the registration form.
        self.assert_register_response_in_pipeline_looks_correct(
            student_views.register_user(strategy.request), pipeline.get(request)['kwargs'])

        # Next, we invoke the view that handles the POST. Not all providers
        # supply email. Manually add it as the user would have to; this
        # also serves as a test of overriding provider values. Always provide a
        # password for us to check that we override it properly.
        overridden_password = strategy.request.POST.get('password')
        email = '*****@*****.**'

        if not strategy.request.POST.get('email'):
            strategy.request.POST = self.get_registration_post_vars({'email': email})

        # The user must not exist yet...
        with self.assertRaises(auth_models.User.DoesNotExist):
            self.get_user_by_email(strategy, email)

        # ...but when we invoke create_account the existing edX view will make
        # it, but not social auths. The pipeline creates those later.
        self.assert_json_success_response_looks_correct(student_views.create_account(strategy.request))
        # We've overridden the user's password, so authenticate() with the old
        # value won't work:
        created_user = self.get_user_by_email(strategy, email)
        self.assert_password_overridden_by_pipeline(overridden_password, created_user.username)

        # At this point the user object exists, but there is no associated
        # social auth.
        self.assert_social_auth_does_not_exist_for_user(created_user, strategy)

        # Pick the pipeline back up. This will create the account association
        # and send the user to the dashboard, where the association will be
        # displayed.
        self.assert_redirect_to_dashboard_looks_correct(
            actions.do_complete(strategy, social_views._do_login, user=created_user))
        self.assert_social_auth_exists_for_user(created_user, strategy)
        self.assert_dashboard_response_looks_correct(student_views.dashboard(request), created_user, linked=True)
Beispiel #24
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)
Beispiel #25
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)
Beispiel #26
0
    def test_full_pipeline_succeeds_registering_new_account(self):
        # First, create, the request and strategy that store pipeline state.
        # Mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_REGISTER, redirect_uri='social:complete')
        strategy.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))

        # Begin! Grab the registration page and check the login control on it.
        self.assert_register_response_before_pipeline_looks_correct(self.client.get('/register'))

        # The pipeline starts by a user GETting /auth/login/<provider>.
        # Synthesize that request and check that it redirects to the correct
        # provider page.
        self.assert_redirect_to_provider_looks_correct(self.client.get(
            pipeline.get_login_url(self.PROVIDER_CLASS.NAME, pipeline.AUTH_ENTRY_LOGIN)))

        # Next, the provider makes a request against /auth/complete/<provider>.
        # pylint:disable-msg=protected-access
        self.assert_redirect_to_register_looks_correct(actions.do_complete(strategy, social_views._do_login))

        # At this point we know the pipeline has resumed correctly. Next we
        # fire off the view that displays the registration form.
        self.assert_register_response_in_pipeline_looks_correct(
            student_views.register_user(strategy.request), pipeline.get(request)['kwargs'])

        # Next, we invoke the view that handles the POST. Not all providers
        # supply email. Manually add it as the user would have to; this
        # also serves as a test of overriding provider values. Always provide a
        # password for us to check that we override it properly.
        overridden_password = strategy.request.POST.get('password')
        email = '*****@*****.**'

        if not strategy.request.POST.get('email'):
            strategy.request.POST = self.get_registration_post_vars({'email': email})

        # The user must not exist yet...
        with self.assertRaises(auth_models.User.DoesNotExist):
            self.get_user_by_email(strategy, email)

        # ...but when we invoke create_account the existing edX view will make
        # it, but not social auths. The pipeline creates those later.
        self.assert_json_success_response_looks_correct(student_views.create_account(strategy.request))
        # We've overridden the user's password, so authenticate() with the old
        # value won't work:
        created_user = self.get_user_by_email(strategy, email)
        self.assert_password_overridden_by_pipeline(overridden_password, created_user.username)

        # At this point the user object exists, but there is no associated
        # social auth.
        self.assert_social_auth_does_not_exist_for_user(created_user, strategy)

        # Pick the pipeline back up. This will create the account association
        # and send the user to the dashboard, where the association will be
        # displayed.
        self.assert_redirect_to_dashboard_looks_correct(
            actions.do_complete(strategy, social_views._do_login, user=created_user))
        self.assert_social_auth_exists_for_user(created_user, strategy)
        self.assert_dashboard_response_looks_correct(student_views.dashboard(request), created_user, linked=True)
Beispiel #27
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:
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'], 'http://example.none/auth/custom_auth_entry')

        response = self.client.get(response['Location'])
        self.assertEqual(response.status_code, 200)
        self.assertIn('action="/misc/my-custom-registration-form" method="post"', response.content)
        data_decoded = base64.b64decode(response.context['data'])  # pylint: disable=no-member
        data_parsed = json.loads(data_decoded)
        # The user's details get passed to the custom page as a base64 encoded query parameter:
        self.assertEqual(data_parsed, {
            '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, msg=data_decoded, digestmod=hashlib.sha256).digest()
        self.assertEqual(base64.b64decode(response.context['hmac']), hmac_expected)  # pylint: disable=no-member

        # 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'), {'email': email, 'password': password})
        self.assertEqual(login_response.status_code, 200)

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

        _, strategy = self.get_request_and_strategy()
        self.assert_social_auth_exists_for_user(created_user, strategy)
Beispiel #28
0
    def test_full_pipeline_succeeds_for_linking_account(self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri="social:complete"
        )
        request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
        pipeline.analytics.track = mock.MagicMock()
        request.user = self.create_user_models_for_existing_account(
            strategy, "*****@*****.**", "password", self.get_username(), skip_social_auth=True
        )

        # 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

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

        # First we expect that we're in the unlinked state, and that there
        # really is no association in the backend.
        self.assert_account_settings_context_looks_correct(
            account_settings_context(request), request.user, linked=False
        )
        self.assert_social_auth_does_not_exist_for_user(request.user, strategy)

        # We should be redirected back to the complete page, setting
        # the "logged in" cookie for the marketing site.
        self.assert_logged_in_cookie_redirect(
            actions.do_complete(
                request.backend,
                social_views._do_login,
                request.user,
                None,  # pylint: disable=protected-access
                redirect_field_name=auth.REDIRECT_FIELD_NAME,
            )
        )

        # Set the cookie and try again
        self.set_logged_in_cookies(request)

        # Fire off the auth pipeline to link.
        self.assert_redirect_to_dashboard_looks_correct(
            actions.do_complete(
                request.backend,
                social_views._do_login,
                request.user,
                None,  # pylint: disable=protected-access
                redirect_field_name=auth.REDIRECT_FIELD_NAME,
            )
        )

        # Now we expect to be in the linked state, with a backend entry.
        self.assert_social_auth_exists_for_user(request.user, strategy)
        self.assert_account_settings_context_looks_correct(account_settings_context(request), request.user, linked=True)
Beispiel #29
0
    def test_full_pipeline_succeeds_for_signing_in_to_existing_active_account(self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
        strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
        pipeline.analytics.track = mock.MagicMock()
        user = self.create_user_models_for_existing_account(
            strategy, '*****@*****.**', 'password', self.get_username())
        self.assert_social_auth_exists_for_user(user, strategy)
        self.assertTrue(user.is_active)

        # Begin! Ensure that the login form contains expected controls before
        # the user starts the pipeline.
        self.assert_login_response_before_pipeline_looks_correct(self.client.get('/login'))

        # The pipeline starts by a user GETting /auth/login/<provider>.
        # Synthesize that request and check that it redirects to the correct
        # provider page.
        self.assert_redirect_to_provider_looks_correct(self.client.get(
            pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN)))

        # Next, the provider makes a request against /auth/complete/<provider>
        # to resume the pipeline.
        # pylint: disable=protected-access
        self.assert_redirect_to_login_looks_correct(actions.do_complete(request.backend, social_views._do_login,
                                                                        request=request))

        # At this point we know the pipeline has resumed correctly. Next we
        # fire off the view that displays the login form and posts it via JS.
        with self._patch_edxmako_current_request(strategy.request):
            self.assert_login_response_in_pipeline_looks_correct(signin_user(strategy.request))

        # Next, we invoke the view that handles the POST, and expect it
        # redirects to /auth/complete. In the browser ajax handlers will
        # redirect the user to the dashboard; we invoke it manually here.
        self.assert_json_success_response_looks_correct(login_user(strategy.request))

        # We should be redirected back to the complete page, setting
        # the "logged in" cookie for the marketing site.
        self.assert_logged_in_cookie_redirect(actions.do_complete(
            request.backend, social_views._do_login, request.user, None,  # pylint: disable=protected-access
            redirect_field_name=auth.REDIRECT_FIELD_NAME, request=request
        ))

        # Set the cookie and try again
        self.set_logged_in_cookies(request)

        self.assert_redirect_to_dashboard_looks_correct(
            actions.do_complete(request.backend, social_views._do_login, user=user, request=request))
        self.assert_account_settings_context_looks_correct(account_settings_context(request))
    def test_full_pipeline_succeeds_for_linking_account(self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
        #request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
        #pipeline.analytics.track = mock.MagicMock()
        #request.user = self.create_user_models_for_existing_account(
        #    strategy, '*****@*****.**', 'password', self.get_username(), skip_social_auth=True)

        # 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))
Beispiel #31
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)
Beispiel #32
0
    def test_full_pipeline_succeeds_for_signing_in_to_existing_active_account(
            self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN,
            redirect_uri='social:complete')
        strategy.backend.auth_complete = mock.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)
        self.assertTrue(user.is_active)

        # Begin! Ensure that the login form contains expected controls before
        # the user starts the pipeline.
        self.assert_login_response_before_pipeline_looks_correct(
            self.client.get('/login'))

        # The pipeline starts by a user GETting /auth/login/<provider>.
        # Synthesize that request and check that it redirects to the correct
        # provider page.
        self.assert_redirect_to_provider_looks_correct(
            self.client.get(
                pipeline.get_login_url(self.PROVIDER_CLASS.NAME,
                                       pipeline.AUTH_ENTRY_LOGIN)))

        # Next, the provider makes a request against /auth/complete/<provider>
        # to resume the pipeline.
        # pylint: disable-msg=protected-access
        self.assert_redirect_to_login_looks_correct(
            actions.do_complete(strategy, social_views._do_login))

        mako_middleware_process_request(strategy.request)
        # At this point we know the pipeline has resumed correctly. Next we
        # fire off the view that displays the login form and posts it via JS.
        self.assert_login_response_in_pipeline_looks_correct(
            student_views.signin_user(strategy.request))

        # Next, we invoke the view that handles the POST, and expect it
        # redirects to /auth/complete. In the browser ajax handlers will
        # redirect the user to the dashboard; we invoke it manually here.
        self.assert_json_success_response_looks_correct(
            student_views.login_user(strategy.request))
        self.assert_redirect_to_dashboard_looks_correct(
            actions.do_complete(strategy, social_views._do_login, user=user))
        self.assert_dashboard_response_looks_correct(
            student_views.dashboard(request), user)
Beispiel #33
0
    def test_full_pipeline_succeeds_for_linking_account(self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN,
            redirect_uri='social:complete')
        #request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
        #pipeline.analytics.track = mock.MagicMock()
        #request.user = self.create_user_models_for_existing_account(
        #    strategy, '*****@*****.**', 'password', self.get_username(), skip_social_auth=True)

        # 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))
    def test_full_pipeline_succeeds_for_unlinking_account(self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
        #request.backend.auth_complete = mock.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)

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

        # 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))
Beispiel #35
0
    def test_custom_form_does_not_link_by_email(self):
        self._configure_testshib_provider()
        self._freeze_time(timestamp=1434326820)  # This is the time when the saved request/response was recorded.

        email = '*****@*****.**'
        UserFactory(username='******', email=email, password='******')
        self._verify_user_email(email)
        self._assert_user_exists('myself', have_social=False)

        custom_url = pipeline.get_login_url('saml-testshib', 'custom1')
        self.client.get(custom_url)

        testshib_response = self._fake_testshib_login_and_return()

        # We should be redirected to the custom form since this account is not linked to an edX account, and
        # automatic linking is not enabled for custom1 entrypoint:
        self.assertEqual(testshib_response.status_code, 302)
        self.assertEqual(testshib_response['Location'], self.url_prefix + '/auth/custom_auth_entry')
Beispiel #36
0
    def test_full_pipeline_succeeds_for_unlinking_account(self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN,
            redirect_uri='social:complete')
        #request.backend.auth_complete = mock.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)

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

        # 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))
Beispiel #37
0
    def test_full_pipeline_succeeds_for_signing_in_to_existing_active_account(self):
        # First, create, the request and strategy that store pipeline state,
        # configure the backend, and mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
        strategy.backend.auth_complete = mock.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)
        self.assertTrue(user.is_active)

        # Begin! Ensure that the login form contains expected controls before
        # the user starts the pipeline.
        self.assert_login_response_before_pipeline_looks_correct(self.client.get('/login'))

        # The pipeline starts by a user GETting /auth/login/<provider>.
        # Synthesize that request and check that it redirects to the correct
        # provider page.
        self.assert_redirect_to_provider_looks_correct(self.client.get(
            pipeline.get_login_url(self.PROVIDER_CLASS.NAME, pipeline.AUTH_ENTRY_LOGIN)))

        # Next, the provider makes a request against /auth/complete/<provider>
        # to resume the pipeline.
        # pylint: disable-msg=protected-access
        self.assert_redirect_to_login_looks_correct(actions.do_complete(strategy, social_views._do_login))

        mako_middleware_process_request(strategy.request)
        # At this point we know the pipeline has resumed correctly. Next we
        # fire off the view that displays the login form and posts it via JS.
        self.assert_login_response_in_pipeline_looks_correct(student_views.signin_user(strategy.request))

        # Next, we invoke the view that handles the POST, and expect it
        # redirects to /auth/complete. In the browser ajax handlers will
        # redirect the user to the dashboard; we invoke it manually here.
        self.assert_json_success_response_looks_correct(student_views.login_user(strategy.request))
        self.assert_redirect_to_dashboard_looks_correct(
            actions.do_complete(strategy, social_views._do_login, user=user))
        self.assert_dashboard_response_looks_correct(student_views.dashboard(request), user)
Beispiel #38
0
    def _redirect_to_login(self, request):
        """
        Returns HttpResponseRedirect object, redirecting User to third-party-auth
        identity provider on the basis of 'COLARAZ_AUTH_PROVIDER_BACKEND_NAME' or
        raising Http404 page in-case auth provider isn't configured properly.
        """
        backend_name = getattr(settings, 'COLARAZ_AUTH_PROVIDER_BACKEND_NAME',
                               None)

        if third_party_auth.is_enabled() and backend_name:
            provider = [
                enabled
                for enabled in third_party_auth.provider.Registry.enabled()
                if enabled.backend_name == backend_name
            ]
            fallback_url = configuration_helpers.get_value('AUTH_PROVIDER_FALLBACK_URL') \
                or configuration_helpers.get_value('LMS_BASE')
            if not provider and fallback_url:
                next_url = urlencode({'next': self._get_current_url(request)})
                redirect_url = '//{}?{}'.format(fallback_url, next_url)
                LOGGER.info(
                    'No Auth Provider found, redirecting to "{}"'.format(
                        redirect_url))
                return HttpResponseRedirect(redirect_url)
            elif provider:
                login_url = pipeline.get_login_url(
                    provider[0].provider_id,
                    pipeline.AUTH_ENTRY_LOGIN,
                    redirect_url=request.GET.get('next')
                    if request.GET.get('next') else request.path,
                )
                LOGGER.info('Redirecting User to Auth Provider: {}'.format(
                    backend_name))
                return HttpResponseRedirect(login_url)

        LOGGER.error(
            'Unable to redirect, Auth Provider is not configured properly')
        raise Http404
Beispiel #39
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)
Beispiel #40
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
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'], '/misc/my-custom-sso-error-page')
def login(request):
    """Allow external auth to intercept and handle a login request.

    Arguments:
        request (Request): A request for the login page.

    Returns:
        Response or None

    """
    # Default to a `None` response, indicating that external auth
    # is not handling the request.
    response = None

    if settings.IONISX_AUTH and third_party_auth.is_enabled():
        # IONISx Authentication is enabled so we directly redirect
        # to IONISx portal
        redirect_to = request.GET.get('next')
        redirect_uri = get_login_url('oa2-portal-oauth2', AUTH_ENTRY_LOGIN, redirect_to)
        response = redirect(redirect_uri)

    elif settings.FEATURES['AUTH_USE_CERTIFICATES'] and external_auth.views.ssl_get_cert_from_request(request):
        # SSL login doesn't require a view, so redirect
        # branding and allow that to process the login if it
        # is enabled and the header is in the request.
        response = external_auth.views.redirect_with_get('root', request.GET)
    elif settings.FEATURES.get('AUTH_USE_CAS'):
        # If CAS is enabled, redirect auth handling to there
        response = redirect(reverse('cas-login'))
    elif settings.FEATURES.get('AUTH_USE_SHIB'):
        redirect_to = request.GET.get('next')
        if redirect_to:
            course_id = _parse_course_id_from_string(redirect_to)
            if course_id and _get_course_enrollment_domain(course_id):
                response = external_auth.views.course_specific_login(request, course_id.to_deprecated_string())

    return response
def register(request):
    """Allow external auth to intercept and handle a registration request.

    Arguments:
        request (Request): A request for the registration page.

    Returns:
        Response or None

    """
    response = None

    if settings.IONISX_AUTH and third_party_auth.is_enabled():
        # IONISx Authentication is enabled so we directly redirect
        # to IONISx portal
        redirect_to = request.GET.get('next')
        redirect_uri = get_login_url('oa2-portal-oauth2', AUTH_ENTRY_REGISTER, redirect_to)
        response = redirect(redirect_uri)

    elif settings.FEATURES.get('AUTH_USE_CERTIFICATES_IMMEDIATE_SIGNUP'):
        # Redirect to branding to process their certificate if SSL is enabled
        # and registration is disabled.
        response = external_auth.views.redirect_with_get('root', request.GET)
    return response
Beispiel #43
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 = [(unicode(year), unicode(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 = []

    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],  # pylint: disable=translation-of-non-string
            },
            'language': {
                'options': released_languages(),
            },
            'level_of_education': {
                'options':
                [(choice[0], _(choice[1]))
                 for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES],  # pylint: disable=translation-of-non-string
            },
            '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,
        'enable_account_deletion':
        configuration_helpers.get_value(
            'ENABLE_ACCOUNT_DELETION',
            settings.FEATURES.get('ENABLE_ACCOUNT_DELETION', False)),
        'extended_profile_fields':
        _get_extended_profile_fields(),
    }

    enterprise_customer = get_enterprise_customer_for_learner(
        site=request.site, user=request.user)
    update_account_settings_context_for_enterprise(context,
                                                   enterprise_customer)

    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
Beispiel #44
0
    def test_full_pipeline_succeeds_registering_new_account(self):
        # First, create, the request and strategy that store pipeline state.
        # Mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_REGISTER, redirect_uri='social:complete')
        strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))

        # Begin! Grab the registration page and check the login control on it.
        self.assert_register_response_before_pipeline_looks_correct(self.client.get('/register'))

        # The pipeline starts by a user GETting /auth/login/<provider>.
        # Synthesize that request and check that it redirects to the correct
        # provider page.
        self.assert_redirect_to_provider_looks_correct(self.client.get(
            pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN)))

        # Next, the provider makes a request against /auth/complete/<provider>.
        # pylint: disable=protected-access
        self.assert_redirect_to_register_looks_correct(actions.do_complete(request.backend, social_views._do_login,
                                                                           request=request))

        # At this point we know the pipeline has resumed correctly. Next we
        # fire off the view that displays the registration form.
        with self._patch_edxmako_current_request(request):
            self.assert_register_response_in_pipeline_looks_correct(
                register_user(strategy.request),
                pipeline.get(request)['kwargs'],
                ['name', 'username', 'email']
            )

        # Next, we invoke the view that handles the POST. Not all providers
        # supply email. Manually add it as the user would have to; this
        # also serves as a test of overriding provider values. Always provide a
        # password for us to check that we override it properly.
        overridden_password = strategy.request.POST.get('password')
        email = '*****@*****.**'

        if not strategy.request.POST.get('email'):
            strategy.request.POST = self.get_registration_post_vars({'email': email})

        # The user must not exist yet...
        with self.assertRaises(auth_models.User.DoesNotExist):
            self.get_user_by_email(strategy, email)

        # ...but when we invoke create_account the existing edX view will make
        # it, but not social auths. The pipeline creates those later.
        with self._patch_edxmako_current_request(strategy.request):
            self.assert_json_success_response_looks_correct(create_account(strategy.request))
        # We've overridden the user's password, so authenticate() with the old
        # value won't work:
        created_user = self.get_user_by_email(strategy, email)
        self.assert_password_overridden_by_pipeline(overridden_password, created_user.username)

        # At this point the user object exists, but there is no associated
        # social auth.
        self.assert_social_auth_does_not_exist_for_user(created_user, strategy)

        # We should be redirected back to the complete page, setting
        # the "logged in" cookie for the marketing site.
        self.assert_logged_in_cookie_redirect(actions.do_complete(
            request.backend, social_views._do_login, request.user, None,  # pylint: disable=protected-access
            redirect_field_name=auth.REDIRECT_FIELD_NAME, request=request
        ))

        # Set the cookie and try again
        self.set_logged_in_cookies(request)
        self.assert_redirect_to_dashboard_looks_correct(
            actions.do_complete(strategy.request.backend, social_views._do_login, user=created_user, request=request))
        # Now the user has been redirected to the dashboard. Their third party account should now be linked.
        self.assert_social_auth_exists_for_user(created_user, strategy)
        self.assert_account_settings_context_looks_correct(account_settings_context(request), linked=True)
Beispiel #45
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 = [(unicode(year), unicode(year)) for year in UserProfile.VALID_YEARS]

    context = {
        'auth': {},
        'duplicate_provider': None,
        'fields': {
            'country': {
                'options': list(countries),
            }, 'gender': {
                'options': [(choice[0], _(choice[1])) for choice in UserProfile.GENDER_CHOICES],  # pylint: disable=translation-of-non-string
            }, 'language': {
                'options': released_languages(),
            }, 'level_of_education': {
                'options': [(choice[0], _(choice[1])) for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES],  # pylint: disable=translation-of-non-string
            }, 'password': {
                'url': reverse('password_reset'),
            }, 'year_of_birth': {
                'options': year_of_birth_options,
            }, 'preferred_language': {
                'options': settings.ALL_LANGUAGES,
            }
        },
        'platform_name': settings.PLATFORM_NAME,
        '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,
    }

    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.
            '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'),
            ),
            # 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),
        } for state in auth_states]

    return context
Beispiel #46
0
    def test_full_pipeline_succeeds_registering_new_account(self):
        # First, create, the request and strategy that store pipeline state.
        # Mock out wire traffic.
        request, strategy = self.get_request_and_strategy(
            auth_entry=pipeline.AUTH_ENTRY_REGISTER, redirect_uri='social:complete')
        strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))

        # Begin! Grab the registration page and check the login control on it.
        self.assert_register_response_before_pipeline_looks_correct(self.client.get('/register'))

        # The pipeline starts by a user GETting /auth/login/<provider>.
        # Synthesize that request and check that it redirects to the correct
        # provider page.
        self.assert_redirect_to_provider_looks_correct(self.client.get(
            pipeline.get_login_url(self.provider.provider_id, pipeline.AUTH_ENTRY_LOGIN)))

        # Next, the provider makes a request against /auth/complete/<provider>.
        # pylint: disable=protected-access
        self.assert_redirect_to_register_looks_correct(actions.do_complete(request.backend, social_views._do_login,
                                                                           request=request))

        # At this point we know the pipeline has resumed correctly. Next we
        # fire off the view that displays the registration form.
        with self._patch_edxmako_current_request(request):
            self.assert_register_response_in_pipeline_looks_correct(
                register_user(strategy.request),
                pipeline.get(request)['kwargs'],
                ['name', 'username', 'email']
            )

        # Next, we invoke the view that handles the POST. Not all providers
        # supply email. Manually add it as the user would have to; this
        # also serves as a test of overriding provider values. Always provide a
        # password for us to check that we override it properly.
        overridden_password = strategy.request.POST.get('password')
        email = '*****@*****.**'

        if not strategy.request.POST.get('email'):
            strategy.request.POST = self.get_registration_post_vars({'email': email})

        # The user must not exist yet...
        with self.assertRaises(auth_models.User.DoesNotExist):
            self.get_user_by_email(strategy, email)

        # ...but when we invoke create_account the existing edX view will make
        # it, but not social auths. The pipeline creates those later.
        with self._patch_edxmako_current_request(strategy.request):
            self.assert_json_success_response_looks_correct(create_account(strategy.request))
        # We've overridden the user's password, so authenticate() with the old
        # value won't work:
        created_user = self.get_user_by_email(strategy, email)
        self.assert_password_overridden_by_pipeline(overridden_password, created_user.username)

        # At this point the user object exists, but there is no associated
        # social auth.
        self.assert_social_auth_does_not_exist_for_user(created_user, strategy)

        # We should be redirected back to the complete page, setting
        # the "logged in" cookie for the marketing site.
        self.assert_logged_in_cookie_redirect(actions.do_complete(
            request.backend, social_views._do_login, request.user, None,  # pylint: disable=protected-access
            redirect_field_name=auth.REDIRECT_FIELD_NAME, request=request
        ))

        # Set the cookie and try again
        self.set_logged_in_cookies(request)
        self.assert_redirect_after_pipeline_completes(
            actions.do_complete(strategy.request.backend, social_views._do_login, user=created_user, request=request))
        # Now the user has been redirected to the dashboard. Their third party account should now be linked.
        self.assert_social_auth_exists_for_user(created_user, strategy)
        self.assert_account_settings_context_looks_correct(account_settings_context(request), linked=True)
Beispiel #47
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 = [(unicode(year), unicode(year))
                             for year in UserProfile.VALID_YEARS]

    comuni_options = (("0", "Ninguna"), ("1", "Andalucía"), ("2", "Aragón"),
                      ("3", "Asturias"), ("4", "Baleares"), ("5", "Canarias"),
                      ("6", "Cantabria"), ("7", "Castilla y León"),
                      ("8", "Castilla - La Mancha"), ("9", "Cataluña"),
                      ("10", "Extremadura"), ("11", "Galicia"),
                      ("12", "Madrid"), ("13", "Murcia"), ("14", "Navarra"),
                      ("15", "País Vasco"), ("16", "La Rioja"),
                      ("17", "Valencia"), ("18", "Ceuta"), ("19", "Melilla"))
    esdoce_options = (("1", _("Yes")), ("2", _("No")))
    camp2_options = (("0", "--"), ("1",
                                   pgettext("Educational institution",
                                            "State")), ("2", _("Subsidized")),
                     ("3", _("Private")))

    context = {
        'auth': {},
        'duplicate_provider':
        None,
        'fields': {
            'country': {
                'options': list(countries),
            },
            'gender': {
                'options': [(choice[0], _(choice[1]))
                            for choice in UserProfile.GENDER_CHOICES],  # pylint: disable=translation-of-non-string
            },
            'language': {
                'options': released_languages(),
            },
            'level_of_education': {
                'options':
                [(choice[0], _(choice[1]))
                 for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES],  # pylint: disable=translation-of-non-string
            },
            'password': {
                'url': reverse('password_reset'),
            },
            'year_of_birth': {
                'options': year_of_birth_options,
            },
            'preferred_language': {
                'options': settings.ALL_LANGUAGES,
            },
            'comuni': {
                'options': comuni_options,
            },
            'esdoce': {
                'options': esdoce_options,
            },
            'camp2': {
                'options': camp2_options,
            }
        },
        'platform_name':
        settings.PLATFORM_NAME,
        '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,
    }

    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),
            } for state in auth_states
        ]

    return context
Beispiel #48
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,
        "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,
                "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:
                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"] = _(six.text_type(msg))  # pylint: disable=E7610
                break

    return context
Beispiel #49
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 = [(unicode(year), unicode(year))
                             for year in UserProfile.VALID_YEARS]

    context = {
        'auth': {},
        'duplicate_provider':
        None,
        'fields': {
            'country': {
                'options': list(countries),
            },
            'gender': {
                'options': [(choice[0], _(choice[1]))
                            for choice in UserProfile.GENDER_CHOICES],  # pylint: disable=translation-of-non-string
            },
            'language': {
                'options': released_languages(),
            },
            'level_of_education': {
                'options':
                [(choice[0], _(choice[1]))
                 for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES],  # pylint: disable=translation-of-non-string
            },
            'password': {
                'url': reverse('password_reset'),
            },
            'year_of_birth': {
                'options': year_of_birth_options,
            },
            'preferred_language': {
                'options': settings.ALL_LANGUAGES,
            }
        },
        'platform_name':
        settings.PLATFORM_NAME,
        'user_accounts_api_url':
        reverse("accounts_api", kwargs={'username': user.username}),
        'user_preferences_api_url':
        reverse('preferences_api', kwargs={'username': user.username}),
    }

    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.
                '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'),
                ),
                # 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),
            } for state in auth_states
        ]

    return context
Beispiel #50
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
    if request.user.is_authenticated():
        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:
        try:
            next_args = urlparse.parse_qs(urlparse.urlparse(redirect_to).query)
            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.
                    return redirect(
                        pipeline.get_login_url(provider_id,
                                               pipeline.AUTH_ENTRY_LOGIN,
                                               redirect_url=redirect_to))
                third_party_auth_hint = provider_id
                initial_mode = "hinted_login"
        except (KeyError, ValueError, IndexError):
            pass

    # If this is a themed site, revert to the old login/registration pages.
    # We need to do this for now to support existing themes.
    # Themed sites can use the new logistration page by setting
    # 'ENABLE_COMBINED_LOGIN_REGISTRATION' in their
    # configuration settings.
    if is_request_in_themed_site() and not configuration_helpers.get_value(
            'ENABLE_COMBINED_LOGIN_REGISTRATION', False):
        if initial_mode == "login":
            return old_login_view(request)
        elif initial_mode == "register":
            return old_register_view(request)

    # Allow external auth to intercept and handle the request
    ext_auth_response = _external_auth_intercept(request, initial_mode)
    if ext_auth_response is not None:
        return ext_auth_response

    # 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]

    # 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,

            # 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))
        },
        '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']),
    }

    context = update_context_for_enterprise(request, context)

    response = render_to_response('student_account/login_and_register.html',
                                  context)

    # 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),
    )

    return response
Beispiel #51
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,
        "providers": [],
        "secondaryProviders": [],
        "finishAuthUrl": None,
        "errorMessage": None,
        "registerFormSubmitButtonText": _("Create Account"),
    }

    if third_party_auth.is_enabled():
        enterprise_customer = enterprise_customer_for_request(request)
        if not enterprise_customer:
            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,
                    "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)

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

                if current_provider.skip_registration_form:
                    # 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.
                    if not enterprise_customer:
                        # As a reliable way of "skipping" the registration form, we just submit it automatically
                        context["autoSubmitRegForm"] = True
                    else:
                        context["autoRegisterWelcomeMessage"] = (
                            'Thank you for joining {}. '
                            'Just a couple steps before you start learning!'
                        ).format(
                            configuration_helpers.get_value(
                                'PLATFORM_NAME', settings.PLATFORM_NAME))
                        context["registerFormSubmitButtonText"] = _("Continue")

        # 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'] = _(unicode(msg))  # pylint: disable=translation-of-non-string
                break

    return context
Beispiel #52
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,
        "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,
                "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:
                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'] = _(unicode(msg))
                break

    return context
Beispiel #53
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
    if request.user.is_authenticated:
        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:
        try:
            next_args = urlparse.parse_qs(urlparse.urlparse(redirect_to).query)
            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.error("Unknown tpa_hint provider: %s", ex)

    # If this is a themed site, revert to the old login/registration pages.
    # We need to do this for now to support existing themes.
    # Themed sites can use the new logistration page by setting
    # 'ENABLE_COMBINED_LOGIN_REGISTRATION' in their
    # configuration settings.
    if is_request_in_themed_site() and not configuration_helpers.get_value('ENABLE_COMBINED_LOGIN_REGISTRATION', False):
        if initial_mode == "login":
            return old_login_view(request)
        elif initial_mode == "register":
            return old_register_view(request)

    # Allow external auth to intercept and handle the request
    ext_auth_response = _external_auth_intercept(request, initial_mode)
    if ext_auth_response is not None:
        return ext_auth_response

    # 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
    ]

    # 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,

            # 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))
        },
        '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']
        ),
    }

    enterprise_customer = enterprise_customer_for_request(request)
    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
Beispiel #54
0
    def test_full_pipeline_succeeds_for_unlinking_testshib_account(
        self,
        mock_get_enterprise_customer_for_learner_settings_view,
        mock_get_enterprise_customer_for_learner,
        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)
        self.assertTrue(
            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_get_enterprise_customer_for_learner.return_value = enterprise_customer_data
        mock_get_enterprise_customer_for_learner_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)
            self.assertNotEqual(
                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)
            self.assertEqual(
                EnterpriseCustomerUser.objects.filter(
                    enterprise_customer=enterprise_customer,
                    user_id=user.id).count(), 0)
Beispiel #55
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 = [(unicode(year), unicode(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 = []

    context = {
        'auth': {},
        'duplicate_provider': None,
        'fields': {
            'country': {
                'options': list(countries),
            }, 'gender': {
                'options': [(choice[0], _(choice[1])) for choice in UserProfile.GENDER_CHOICES],  # pylint: disable=translation-of-non-string
            }, 'language': {
                'options': released_languages(),
            }, 'level_of_education': {
                'options': [(choice[0], _(choice[1])) for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES],  # pylint: disable=translation-of-non-string
            }, 'password': {
                'url': reverse('password_reset'),
            }, 'year_of_birth': {
                'options': year_of_birth_options,
            }, 'preferred_language': {
                'options': all_languages(),
            }, 'time_zone': {
                'options': UserPreference.TIME_ZONE_CHOICES,
                'enabled': settings.FEATURES.get('ENABLE_TIME_ZONE_PREFERENCE'),
            }
        },
        'platform_name': get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME),
        '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.current().show_program_listing,
        'order_history': user_orders
    }

    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),
        } for state in auth_states]

    return context
Beispiel #56
0
 def assert_signin_button_looks_functional(self, content, auth_entry):
     """Asserts JS is available to signin buttons and has the right args."""
     self.assertTrue(re.search(r'function thirdPartySignin', content))
     self.assertEqual(
         pipeline.get_login_url(self.PROVIDER_CLASS.NAME, auth_entry),
         re.search(r"thirdPartySignin\(event, '([^']+)", content).groups()[0])
Beispiel #57
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:
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'],
                         'http://example.none/auth/custom_auth_entry')

        response = self.client.get(response['Location'])
        self.assertEqual(response.status_code, 200)
        self.assertIn(
            'action="/misc/my-custom-registration-form" method="post"',
            response.content)
        data_decoded = base64.b64decode(response.context['data'])  # pylint: disable=no-member
        data_parsed = json.loads(data_decoded)
        # The user's details get passed to the custom page as a base64 encoded query parameter:
        self.assertEqual(
            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,
                                 msg=data_decoded,
                                 digestmod=hashlib.sha256).digest()
        self.assertEqual(base64.b64decode(response.context['hmac']),
                         hmac_expected)  # pylint: disable=no-member

        # 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'), {
            'email': email,
            'password': password
        })
        self.assertEqual(login_response.status_code, 200)

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

        _, strategy = self.get_request_and_strategy()
        self.assert_social_auth_exists_for_user(created_user, strategy)
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 = [(unicode(year), unicode(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 = get_enterprise_customer_for_learner(user=request.user)
    update_account_settings_context_for_enterprise(context, enterprise_customer)

    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
Beispiel #59
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:
        try:
            next_args = six.moves.urllib.parse.parse_qs(six.moves.urllib.parse.urlparse(redirect_to).query)
            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)

    # We are defaulting to true because all themes should now be using the newer page.
    if is_request_in_themed_site() and not configuration_helpers.get_value('ENABLE_COMBINED_LOGIN_REGISTRATION', True):
        if initial_mode == "login":
            return old_login_view(request)
        elif initial_mode == "register":
            return old_register_view(request)

    # 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()
        },
        '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']
        ),
    }

    enterprise_customer = enterprise_customer_for_request(request)
    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
Beispiel #60
0
def account_settings_context(request):
    """ Context for the account settings page.

    Args:
        request: The request object.

    Returns:
        dict

    """
    user = request.user

    country_options = [
        (country_code, _(country_name))  # pylint: disable=translation-of-non-string
        for country_code, country_name in sorted(countries.countries, key=lambda (__, name): unicode(name))
    ]

    year_of_birth_options = [(unicode(year), unicode(year)) for year in UserProfile.VALID_YEARS]

    context = {
        "auth": {},
        "duplicate_provider": None,
        "fields": {
            "country": {"options": country_options},
            "gender": {
                "options": [
                    (choice[0], _(choice[1])) for choice in UserProfile.GENDER_CHOICES
                ]  # pylint: disable=translation-of-non-string
            },
            "language": {"options": released_languages()},
            "level_of_education": {
                "options": [
                    (choice[0], _(choice[1])) for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES
                ]  # pylint: disable=translation-of-non-string
            },
            "password": {"url": reverse("password_reset")},
            "year_of_birth": {"options": year_of_birth_options},
            "preferred_language": {"options": settings.ALL_LANGUAGES},
        },
        "platform_name": settings.PLATFORM_NAME,
        "user_accounts_api_url": reverse("accounts_api", kwargs={"username": user.username}),
        "user_preferences_api_url": reverse("preferences_api", kwargs={"username": user.username}),
    }

    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"] = [
            {
                "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.
                "connect_url": pipeline.get_login_url(
                    state.provider.NAME,
                    pipeline.AUTH_ENTRY_ACCOUNT_SETTINGS,
                    # The url the user should be directed to after the auth process has completed.
                    redirect_url=reverse("account_settings"),
                ),
                # 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.NAME),
            }
            for state in auth_states
        ]

    try:
        external_auth_map = ExternalAuthMap.objects.get(user=user)
    except:
        external_auth_map = None
    context["is_shib_auth"] = "shib" in external_auth_map.external_domain if external_auth_map else False

    return context