Ejemplo n.º 1
0
def inactive_user_view(request):
    """
    A newly or recently registered user has completed the social auth pipeline.
    Their account is not yet activated, but we let them login since the third party auth
    provider is trusted to vouch for them. See details in pipeline.py.

    The reason this view exists is that if we don't define this as the
    SOCIAL_AUTH_INACTIVE_USER_URL, inactive users will get sent to LOGIN_ERROR_URL, which we
    don't want.

    If the third_party_provider.skip_email_verification is set then the user is activated
    and verification email is not sent
    """
    # 'next' may be set to '/account/finish_auth/.../' if this user needs to be auto-enrolled
    # in a course. Otherwise, just redirect them to the dashboard, which displays a message
    # about activating their account.
    user = request.user
    profile = UserProfile.objects.get(user=user)
    activated = user.is_active
    # If the user is registering via 3rd party auth, track which provider they use
    if third_party_auth.is_enabled() and pipeline.running(request):
        running_pipeline = pipeline.get(request)
        third_party_provider = provider.Registry.get_from_pipeline(
            running_pipeline)
        if third_party_provider.skip_email_verification and not activated:
            user.is_active = True
            user.save()
            activated = True
    if not activated:
        send_account_activation_email(user, profile)

    return redirect(request.GET.get('next', 'dashboard'))
Ejemplo n.º 2
0
def _link_user_to_third_party_provider(
    is_third_party_auth_enabled,
    third_party_auth_credentials_in_api,
    user,
    request,
    params,
):
    """
    If a 3rd party auth provider and credentials were provided in the API, link the account with social auth
    (If the user is using the normal register page, the social auth pipeline does the linking, not this code)

    Note: this is orthogonal to the 3rd party authentication pipeline that occurs
    when the account is created via the browser and redirect URLs.
    """
    third_party_provider, running_pipeline = None, None
    if is_third_party_auth_enabled and third_party_auth_credentials_in_api:
        backend_name = params['provider']
        request.social_strategy = social_utils.load_strategy(request)
        redirect_uri = reverse('social:complete', args=(backend_name, ))
        request.backend = social_utils.load_backend(request.social_strategy,
                                                    backend_name, redirect_uri)
        social_access_token = params.get('access_token')
        if not social_access_token:
            raise ValidationError({
                'access_token': [
                    _(u"An access_token is required when passing value ({}) for provider."
                      ).format(params['provider'])
                ]
            })
        request.session[
            pipeline.AUTH_ENTRY_KEY] = pipeline.AUTH_ENTRY_REGISTER_API
        pipeline_user = None
        error_message = ""
        try:
            pipeline_user = request.backend.do_auth(social_access_token,
                                                    user=user)
        except AuthAlreadyAssociated:
            error_message = _(
                "The provided access_token is already associated with another user."
            )
        except (HTTPError, AuthException):
            error_message = _("The provided access_token is not valid.")
        if not pipeline_user or not isinstance(pipeline_user, User):
            # Ensure user does not re-enter the pipeline
            request.social_strategy.clean_partial_pipeline(social_access_token)
            raise ValidationError({'access_token': [error_message]})

    # If the user is registering via 3rd party auth, track which provider they use
    if is_third_party_auth_enabled and pipeline.running(request):
        running_pipeline = pipeline.get(request)
        third_party_provider = provider.Registry.get_from_pipeline(
            running_pipeline)

    return third_party_provider, running_pipeline
Ejemplo n.º 3
0
def _do_third_party_auth(request):
    """
    User is already authenticated via 3rd party, now try to find and return their associated Django user.
    """
    running_pipeline = pipeline.get(request)
    username = running_pipeline['kwargs'].get('username')
    backend_name = running_pipeline['backend']
    third_party_uid = running_pipeline['kwargs']['uid']
    requested_provider = provider.Registry.get_from_pipeline(running_pipeline)
    platform_name = configuration_helpers.get_value("platform_name",
                                                    settings.PLATFORM_NAME)

    try:
        return pipeline.get_authenticated_user(requested_provider, username,
                                               third_party_uid)
    except User.DoesNotExist:
        AUDIT_LOG.info(
            u"Login failed - user with username {username} has no social auth "
            u"with backend_name {backend_name}".format(
                username=username, backend_name=backend_name))
        message = Text(
            _(u"You've successfully signed in to your {provider_name} account, "
              u"but this account isn't linked with your {platform_name} account yet. {blank_lines}"
              u"Use your {platform_name} username and password to sign in to {platform_name} below, "
              u"and then link your {platform_name} account with {provider_name} from your dashboard. {blank_lines}"
              u"If you don't have an account on {platform_name} yet, "
              u"click {register_label_strong} at the top of the page.")
        ).format(blank_lines=HTML('<br/><br/>'),
                 platform_name=platform_name,
                 provider_name=requested_provider.name,
                 register_label_strong=HTML(
                     '<strong>{register_text}</strong>').format(
                         register_text=_('Register')))

        raise AuthFailedError(
            message, error_code='third-party-auth-with-no-linked-account')
Ejemplo n.º 4
0
def login_user(request):
    """
    AJAX request to log in the user.

    Arguments:
        request (HttpRequest)

    Required params:
        email, password

    Optional params:
        analytics: a JSON-encoded object with additional info to include in the login analytics event. The only
            supported field is "enroll_course_id" to indicate that the user logged in while enrolling in a particular
            course.

    Returns:
        HttpResponse: 200 if successful.
            Ex. {'success': true}
        HttpResponse: 400 if the request failed.
            Ex. {'success': false, 'value': '{'success': false, 'value: 'Email or password is incorrect.'}
        HttpResponse: 403 if successful authentication with a third party provider but does not have a linked account.
            Ex. {'success': false, 'error_code': 'third-party-auth-with-no-linked-account'}

    Example Usage:

        POST /login_ajax
        with POST params `email`, `password`

        200 {'success': true}

    """
    _parse_analytics_param_for_course_id(request)

    third_party_auth_requested = third_party_auth.is_enabled(
    ) and pipeline.running(request)
    first_party_auth_requested = bool(request.POST.get('email')) or bool(
        request.POST.get('password'))
    is_user_third_party_authenticated = False

    set_custom_attribute('login_user_course_id', request.POST.get('course_id'))

    try:
        if third_party_auth_requested and not first_party_auth_requested:
            # The user has already authenticated via third-party auth and has not
            # asked to do first party auth by supplying a username or password. We
            # now want to put them through the same logging and cookie calculation
            # logic as with first-party auth.

            # This nested try is due to us only returning an HttpResponse in this
            # one case vs. JsonResponse everywhere else.
            try:
                user = _do_third_party_auth(request)
                is_user_third_party_authenticated = True
                set_custom_attribute('login_user_tpa_success', True)
            except AuthFailedError as e:
                set_custom_attribute('login_user_tpa_success', False)
                set_custom_attribute('login_user_tpa_failure_msg', e.value)

                # user successfully authenticated with a third party provider, but has no linked Open edX account
                response_content = e.get_response()
                return JsonResponse(response_content, status=403)
        else:
            user = _get_user_by_email(request)

        _check_excessive_login_attempts(user)

        possibly_authenticated_user = user

        if not is_user_third_party_authenticated:
            possibly_authenticated_user = _authenticate_first_party(
                request, user, third_party_auth_requested)
            if possibly_authenticated_user and password_policy_compliance.should_enforce_compliance_on_login(
            ):
                # Important: This call must be made AFTER the user was successfully authenticated.
                _enforce_password_policy_compliance(
                    request, possibly_authenticated_user)

        if possibly_authenticated_user is None or not possibly_authenticated_user.is_active:
            _handle_failed_authentication(user, possibly_authenticated_user)

        _handle_successful_authentication_and_login(
            possibly_authenticated_user, request)

        redirect_url = None  # The AJAX method calling should know the default destination upon success
        if is_user_third_party_authenticated:
            running_pipeline = pipeline.get(request)
            redirect_url = pipeline.get_complete_url(
                backend_name=running_pipeline['backend'])

        elif should_redirect_to_logistration_mircrofrontend():
            redirect_url = get_next_url_for_login_page(request,
                                                       include_host=True)

        response = JsonResponse({
            'success': True,
            'redirect_url': redirect_url,
        })

        # Ensure that the external marketing site can
        # detect that the user is logged in.
        response = set_logged_in_cookies(request, response,
                                         possibly_authenticated_user)
        set_custom_attribute('login_user_auth_failed_error', False)
        set_custom_attribute('login_user_response_status',
                             response.status_code)
        set_custom_attribute('login_user_redirect_url', redirect_url)
        return response
    except AuthFailedError as error:
        response_content = error.get_response()
        log.exception(response_content)
        if response_content.get('error_code') == 'inactive-user':
            response_content['email'] = user.email

        response = JsonResponse(response_content, status=400)
        set_custom_attribute('login_user_auth_failed_error', True)
        set_custom_attribute('login_user_response_status',
                             response.status_code)
        return response
Ejemplo n.º 5
0
def login_and_registration_form(request, initial_mode="login"):
    """Render the combined login/registration form, defaulting to login

    This relies on the JS to asynchronously load the actual form from
    the user_api.

    Keyword Args:
        initial_mode (string): Either "login" or "register".

    """
    # Determine the URL to redirect to following login/registration/third_party_auth
    redirect_to = get_next_url_for_login_page(request)

    # If we're already logged in, redirect to the dashboard
    # Note: We check for the existence of login-related cookies in addition to is_authenticated
    #  since Django's SessionAuthentication middleware auto-updates session cookies but not
    #  the other login-related cookies. See ARCH-282.
    if request.user.is_authenticated and are_logged_in_cookies_set(request):
        return redirect(redirect_to)

    # Retrieve the form descriptions from the user API
    form_descriptions = _get_form_descriptions(request)

    # Our ?next= URL may itself contain a parameter 'tpa_hint=x' that we need to check.
    # If present, we display a login page focused on third-party auth with that provider.
    third_party_auth_hint = None
    if '?' in redirect_to:  # lint-amnesty, pylint: disable=too-many-nested-blocks
        try:
            next_args = six.moves.urllib.parse.parse_qs(six.moves.urllib.parse.urlparse(redirect_to).query)
            if 'tpa_hint' in next_args:
                provider_id = next_args['tpa_hint'][0]
                tpa_hint_provider = third_party_auth.provider.Registry.get(provider_id=provider_id)
                if tpa_hint_provider:
                    if tpa_hint_provider.skip_hinted_login_dialog:
                        # Forward the user directly to the provider's login URL when the provider is configured
                        # to skip the dialog.
                        if initial_mode == "register":
                            auth_entry = pipeline.AUTH_ENTRY_REGISTER
                        else:
                            auth_entry = pipeline.AUTH_ENTRY_LOGIN
                        return redirect(
                            pipeline.get_login_url(provider_id, auth_entry, redirect_url=redirect_to)
                        )
                    third_party_auth_hint = provider_id
                    initial_mode = "hinted_login"
        except (KeyError, ValueError, IndexError) as ex:
            log.exception(u"Unknown tpa_hint provider: %s", ex)

    # Redirect to authn MFE if it is enabled or user is not an enterprise user or not coming from a SAML IDP.
    saml_provider = False
    running_pipeline = pipeline.get(request)
    enterprise_customer = enterprise_customer_for_request(request)
    if running_pipeline:
        saml_provider, __ = third_party_auth.utils.is_saml_provider(
            running_pipeline.get('backend'), running_pipeline.get('kwargs')
        )

    if should_redirect_to_authn_microfrontend() and not enterprise_customer and not saml_provider:

        # This is to handle a case where a logged-in cookie is not present but the user is authenticated.
        # Note: If we don't handle this learner is redirected to authn MFE and then back to dashboard
        # instead of the desired redirect URL (e.g. finish_auth) resulting in learners not enrolling
        # into the courses.
        if request.user.is_authenticated and redirect_to:
            return redirect(redirect_to)

        query_params = request.GET.urlencode()
        url_path = '/{}{}'.format(
            initial_mode,
            '?' + query_params if query_params else ''
        )
        return redirect(settings.AUTHN_MICROFRONTEND_URL + url_path)

    # Account activation message
    account_activation_messages = [
        {
            'message': message.message, 'tags': message.tags
        } for message in messages.get_messages(request) if 'account-activation' in message.tags
    ]

    account_recovery_messages = [
        {
            'message': message.message, 'tags': message.tags
        } for message in messages.get_messages(request) if 'account-recovery' in message.tags
    ]

    # Otherwise, render the combined login/registration page
    context = {
        'data': {
            'login_redirect_url': redirect_to,
            'initial_mode': initial_mode,
            'third_party_auth': third_party_auth_context(request, redirect_to, third_party_auth_hint),
            'third_party_auth_hint': third_party_auth_hint or '',
            'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
            'support_link': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK),
            'password_reset_support_link': configuration_helpers.get_value(
                'PASSWORD_RESET_SUPPORT_LINK', settings.PASSWORD_RESET_SUPPORT_LINK
            ) or settings.SUPPORT_SITE_LINK,
            'account_activation_messages': account_activation_messages,
            'account_recovery_messages': account_recovery_messages,

            # Include form descriptions retrieved from the user API.
            # We could have the JS client make these requests directly,
            # but we include them in the initial page load to avoid
            # the additional round-trip to the server.
            'login_form_desc': json.loads(form_descriptions['login']),
            'registration_form_desc': json.loads(form_descriptions['registration']),
            'password_reset_form_desc': json.loads(form_descriptions['password_reset']),
            'account_creation_allowed': configuration_helpers.get_value(
                'ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)),
            'is_account_recovery_feature_enabled': is_secondary_email_feature_enabled(),
            'is_multiple_user_enterprises_feature_enabled': is_multiple_user_enterprises_feature_enabled(),
            'enterprise_slug_login_url': get_enterprise_slug_login_url(),
            'is_require_third_party_auth_enabled': is_require_third_party_auth_enabled(),
        },
        'login_redirect_url': redirect_to,  # This gets added to the query string of the "Sign In" button in header
        'responsive': True,
        'allow_iframing': True,
        'disable_courseware_js': True,
        'combined_login_and_register': True,
        'disable_footer': not configuration_helpers.get_value(
            'ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER',
            settings.FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER']
        ),
    }

    update_logistration_context_for_enterprise(request, context, enterprise_customer)

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

    return response
Ejemplo n.º 6
0
def third_party_auth_context(request, redirect_to, tpa_hint=None):
    """
    Context for third party auth providers and the currently running pipeline.

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

    Returns:
        dict

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

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

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

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

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

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

    return context
Ejemplo n.º 7
0
def login_user(request, api_version='v1'):  # pylint: disable=too-many-statements
    """
    AJAX request to log in the user.

    Arguments:
        request (HttpRequest)

    Required params:
        email, password

    Optional params:
        analytics: a JSON-encoded object with additional info to include in the login analytics event. The only
            supported field is "enroll_course_id" to indicate that the user logged in while enrolling in a particular
            course.

    Returns:
        HttpResponse: 200 if successful.
            Ex. {'success': true}
        HttpResponse: 400 if the request failed.
            Ex. {'success': false, 'value': '{'success': false, 'value: 'Email or password is incorrect.'}
        HttpResponse: 403 if successful authentication with a third party provider but does not have a linked account.
            Ex. {'success': false, 'error_code': 'third-party-auth-with-no-linked-account'}

    Example Usage:

        POST /login_ajax
        with POST params `email`, `password`

        200 {'success': true}

    """
    _parse_analytics_param_for_course_id(request)

    third_party_auth_requested = third_party_auth.is_enabled(
    ) and pipeline.running(request)
    first_party_auth_requested = bool(request.POST.get('email')) or bool(
        request.POST.get('password'))
    is_user_third_party_authenticated = False

    set_custom_attribute('login_user_course_id', request.POST.get('course_id'))

    if is_require_third_party_auth_enabled(
    ) and not third_party_auth_requested:
        return HttpResponseForbidden(
            "Third party authentication is required to login. Username and password were received instead."
        )
    possibly_authenticated_user = None
    try:
        if third_party_auth_requested and not first_party_auth_requested:
            # The user has already authenticated via third-party auth and has not
            # asked to do first party auth by supplying a username or password. We
            # now want to put them through the same logging and cookie calculation
            # logic as with first-party auth.

            # This nested try is due to us only returning an HttpResponse in this
            # one case vs. JsonResponse everywhere else.
            try:
                user = _do_third_party_auth(request)
                is_user_third_party_authenticated = True
                set_custom_attribute('login_user_tpa_success', True)
            except AuthFailedError as e:
                set_custom_attribute('login_user_tpa_success', False)
                set_custom_attribute('login_user_tpa_failure_msg', e.value)
                if e.error_code:
                    set_custom_attribute('login_error_code', e.error_code)

                # user successfully authenticated with a third party provider, but has no linked Open edX account
                response_content = e.get_response()
                return JsonResponse(response_content, status=403)
        else:
            user = _get_user_by_email_or_username(request, api_version)

        _check_excessive_login_attempts(user)

        possibly_authenticated_user = user

        try:
            possibly_authenticated_user = StudentLoginRequested.run_filter(
                user=possibly_authenticated_user)
        except StudentLoginRequested.PreventLogin as exc:
            raise AuthFailedError(
                str(exc),
                redirect_url=exc.redirect_to,
                error_code=exc.error_code,
                context=exc.context,
            ) from exc

        if not is_user_third_party_authenticated:
            possibly_authenticated_user = _authenticate_first_party(
                request, user, third_party_auth_requested)
            if possibly_authenticated_user and password_policy_compliance.should_enforce_compliance_on_login(
            ):
                # Important: This call must be made AFTER the user was successfully authenticated.
                _enforce_password_policy_compliance(
                    request, possibly_authenticated_user)

        if possibly_authenticated_user is None or not (
                possibly_authenticated_user.is_active
                or settings.MARKETING_EMAILS_OPT_IN):
            _handle_failed_authentication(user, possibly_authenticated_user)

        pwned_properties = check_pwned_password_and_send_track_event(
            user.id, request.POST.get('password'),
            user.is_staff) if not is_user_third_party_authenticated else {}
        # Set default for third party login
        password_frequency = pwned_properties.get('frequency', -1)
        if (settings.ENABLE_AUTHN_LOGIN_BLOCK_HIBP_POLICY
                and password_frequency >=
                settings.HIBP_LOGIN_BLOCK_PASSWORD_FREQUENCY_THRESHOLD):
            raise VulnerablePasswordError(
                accounts.AUTHN_LOGIN_BLOCK_HIBP_POLICY_MSG,
                'require-password-change')

        _handle_successful_authentication_and_login(
            possibly_authenticated_user, request)

        # The AJAX method calling should know the default destination upon success
        redirect_url, finish_auth_url = None, ''

        if third_party_auth_requested:
            running_pipeline = pipeline.get(request)
            finish_auth_url = pipeline.get_complete_url(
                backend_name=running_pipeline['backend'])

        if is_user_third_party_authenticated:
            redirect_url = finish_auth_url
        elif should_redirect_to_authn_microfrontend():
            next_url, root_url = get_next_url_for_login_page(request,
                                                             include_host=True)
            redirect_url = get_redirect_url_with_host(
                root_url,
                enterprise_selection_page(request, possibly_authenticated_user,
                                          finish_auth_url or next_url))

        if (settings.ENABLE_AUTHN_LOGIN_NUDGE_HIBP_POLICY
                and 0 <= password_frequency <=
                settings.HIBP_LOGIN_NUDGE_PASSWORD_FREQUENCY_THRESHOLD):
            raise VulnerablePasswordError(
                accounts.AUTHN_LOGIN_NUDGE_HIBP_POLICY_MSG,
                'nudge-password-change', redirect_url)

        response = JsonResponse({
            'success': True,
            'redirect_url': redirect_url,
        })

        # Ensure that the external marketing site can
        # detect that the user is logged in.
        response = set_logged_in_cookies(request, response,
                                         possibly_authenticated_user)
        set_custom_attribute('login_user_auth_failed_error', False)
        set_custom_attribute('login_user_response_status',
                             response.status_code)
        set_custom_attribute('login_user_redirect_url', redirect_url)
        mark_user_change_as_expected(user.id)
        return response
    except AuthFailedError as error:
        response_content = error.get_response()
        log.exception(response_content)

        error_code = response_content.get('error_code')
        if error_code:
            set_custom_attribute('login_error_code', error_code)
        email_or_username_key = 'email' if api_version == API_V1 else 'email_or_username'
        email_or_username = request.POST.get(email_or_username_key, None)
        email_or_username = possibly_authenticated_user.email if possibly_authenticated_user else email_or_username
        response_content['email'] = email_or_username
    except VulnerablePasswordError as error:
        response_content = error.get_response()
        log.exception(response_content)

    response = JsonResponse(response_content, status=400)
    set_custom_attribute('login_user_auth_failed_error', True)
    set_custom_attribute('login_user_response_status', response.status_code)
    return response