def test_complete_url_raises_value_error_if_provider_not_enabled(self):
        provider_name = 'oa2-not-enabled'

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

        with self.assertRaises(ValueError):
            pipeline.get_complete_url(provider_name)
    def test_complete_url_raises_value_error_if_provider_not_enabled(self):
        provider_name = 'oa2-not-enabled'

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

        with self.assertRaises(ValueError):
            pipeline.get_complete_url(provider_name)
    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 #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": [],
        "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 #6
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 #7
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 #8
0
 def get_context(self,
                 params=None,
                 current_provider=None,
                 backend_name=None,
                 add_user_details=False):
     """
     Returns the third party auth context
     """
     return {
         'currentProvider':
         current_provider,
         'providers':
         self.get_provider_data(params) if params else [],
         'secondaryProviders': [],
         'finishAuthUrl':
         pipeline.get_complete_url(backend_name) if backend_name else None,
         'errorMessage':
         None,
         'registerFormSubmitButtonText':
         'Create Account',
         'syncLearnerProfileData':
         False,
         'pipeline_user_details': {
             'email': '*****@*****.**'
         } if add_user_details else {}
     }
Beispiel #9
0
 def assert_json_success_response_looks_correct(self, response):
     """Asserts the json response indicates success and redirection."""
     self.assertEqual(200, response.status_code)
     payload = json.loads(response.content)
     self.assertTrue(payload.get('success'))
     self.assertEqual(pipeline.get_complete_url(self.provider.backend_name),
                      payload.get('redirect_url'))
Beispiel #10
0
def login_user(request):
    """
    AJAX request to log in the user.
    """
    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

    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
            except AuthFailedError as e:
                return HttpResponse(e.value, content_type="text/plain", status=403)
        else:
            user = _get_user_by_email(request)

        _check_shib_redirect(user)
        _check_excessive_login_attempts(user)

        possibly_authenticated_user = user

        if not is_user_third_party_authenticated:
            possibly_authenticated_user = _authenticate_first_party(request, user)
            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)

        if not is_edly_user_allowed_to_login(request, possibly_authenticated_user):
            raise AuthFailedError(_('You are not allowed to login on this site.'))

        _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'])

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

        # Ensure that the external marketing site can
        # detect that the user is logged in.
        return set_logged_in_cookies(request, response, possibly_authenticated_user)
    except AuthFailedError as error:
        log.exception(error.get_response())
        return JsonResponse(error.get_response())
Beispiel #11
0
    def get_request_and_strategy(self, auth_entry=None, redirect_uri=None):
        """Gets a fully-configured request and strategy.

        These two objects contain circular references, so we create them
        together. The references themselves are a mixture of normal __init__
        stuff and monkey-patching done by python-social-auth. See, for example,
        social.apps.django_apps.utils.strategy().
        """
        request = self.request_factory.get(
            pipeline.get_complete_url(self.backend_name) +
            '?redirect_state=redirect_state_value&code=code_value&state=state_value'
        )
        request.user = auth_models.AnonymousUser()
        request.session = cache.SessionStore()
        request.session[self.backend_name + '_state'] = 'state_value'

        if auth_entry:
            request.session[pipeline.AUTH_ENTRY_KEY] = auth_entry

        strategy = social_utils.load_strategy(request=request)
        request.social_strategy = strategy
        request.backend = social_utils.load_backend(strategy,
                                                    self.backend_name,
                                                    redirect_uri)

        return request, strategy
Beispiel #12
0
 def assert_logged_in_cookie_redirect(self, response):
     """Verify that the user was redirected in order to set the logged in cookie. """
     self.assertEqual(response.status_code, 302)
     self.assertEqual(
         response["Location"],
         pipeline.get_complete_url(self.PROVIDER_CLASS.BACKEND_CLASS.name)
     )
     self.assertEqual(response.cookies[django_settings.EDXMKTG_COOKIE_NAME].value, 'true')
Beispiel #13
0
 def assert_logged_in_cookie_redirect(self, response):
     """Verify that the user was redirected in order to set the logged in cookie. """
     self.assertEqual(response.status_code, 302)
     self.assertEqual(
         response["Location"],
         pipeline.get_complete_url(self.PROVIDER_CLASS.BACKEND_CLASS.name)
     )
     self.assertEqual(response.cookies[django_settings.EDXMKTG_COOKIE_NAME].value, 'true')
Beispiel #14
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 #15
0
def login_user(request):
    """
    AJAX request to log in the user.
    """
    third_party_auth_requested = third_party_auth.is_enabled() and pipeline.running(request)
    trumped_by_first_party_auth = bool(request.POST.get('email')) or bool(request.POST.get('password'))
    was_authenticated_third_party = False

    try:
        if third_party_auth_requested and not trumped_by_first_party_auth:
            # 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:
                email_user = _do_third_party_auth(request)
                was_authenticated_third_party = True
            except AuthFailedError as e:
                return HttpResponse(e.value, content_type="text/plain", status=403)
        else:
            email_user = _get_user_by_email(request)

        _check_shib_redirect(email_user)
        _check_excessive_login_attempts(email_user)
        _check_forced_password_reset(email_user)

        possibly_authenticated_user = email_user

        if not was_authenticated_third_party:
            possibly_authenticated_user = _authenticate_first_party(request, email_user)
            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(email_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 was_authenticated_third_party:
            running_pipeline = pipeline.get(request)
            redirect_url = pipeline.get_complete_url(backend_name=running_pipeline['backend'])

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

        # Ensure that the external marketing site can
        # detect that the user is logged in.
        return set_logged_in_cookies(request, response, possibly_authenticated_user)
    except AuthFailedError as error:
        log.exception(error.get_response())
        return JsonResponse(error.get_response())
    def _fake_strategy(self):
        """Simulate the strategy passed to the pipeline step. """
        request = RequestFactory().get(
            pipeline.get_complete_url(self.BACKEND_NAME))
        request.user = self.user
        request.session = cache.SessionStore()

        return social_utils.load_strategy(backend=self.BACKEND_NAME,
                                          request=request)
Beispiel #17
0
 def assert_logged_in_cookie_redirect(self, response):
     """Verify that the user was redirected in order to set the logged in cookie. """
     self.assertEqual(response.status_code, 302)
     self.assertEqual(
         response["Location"],
         pipeline.get_complete_url(self.provider.backend_name)
     )
     self.assertEqual(response.cookies[django_settings.EDXMKTG_LOGGED_IN_COOKIE_NAME].value, 'true')
     self.assertIn(django_settings.EDXMKTG_USER_INFO_COOKIE_NAME, response.cookies)
    def _fake_strategy(self):
        """Simulate the strategy passed to the pipeline step. """
        request = RequestFactory().get(pipeline.get_complete_url(self.BACKEND_NAME))
        request.user = self.user
        request.session = cache.SessionStore()

        return social_utils.load_strategy(
            backend=self.BACKEND_NAME, request=request
        )
Beispiel #19
0
 def assert_logged_in_cookie_redirect(self, response):
     """Verify that the user was redirected in order to set the logged in cookie. """
     self.assertEqual(response.status_code, 302)
     self.assertEqual(
         response["Location"],
         pipeline.get_complete_url(self.provider.backend_name)
     )
     self.assertEqual(response.cookies[django_settings.EDXMKTG_LOGGED_IN_COOKIE_NAME].value, 'true')
     self.assertIn(django_settings.EDXMKTG_USER_INFO_COOKIE_NAME, response.cookies)
Beispiel #20
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
Beispiel #21
0
def course(request):
    response_format = request.GET.get('format', 'html')
    if request.method == 'GET' and response_format == 'html' and 'application/json' not in request.META.get('HTTP_ACCEPT', 'application/json'):
        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:
                redirect_to = pipeline.get_complete_url(current_provider.backend_name)
                return redirect(redirect_to)

    return course_handler(request)
Beispiel #22
0
def login_user(request):
    """
    AJAX request to log in the user.
    """
    third_party_auth_requested = third_party_auth.is_enabled() and pipeline.running(request)
    trumped_by_first_party_auth = bool(request.POST.get('email')) or bool(request.POST.get('password'))
    was_authenticated_third_party = False

    try:
        if third_party_auth_requested and not trumped_by_first_party_auth:
            # 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:
                email_user = _do_third_party_auth(request)
                was_authenticated_third_party = True
            except AuthFailedError as e:
                return HttpResponse(e.value, content_type="text/plain", status=403)
        else:
            email_user = _get_user_by_email(request)

        _check_shib_redirect(email_user)
        _check_excessive_login_attempts(email_user)
        _check_forced_password_reset(email_user)

        possibly_authenticated_user = email_user

        if not was_authenticated_third_party:
            possibly_authenticated_user = _authenticate_first_party(request, email_user)

        if possibly_authenticated_user is None or not possibly_authenticated_user.is_active:
            _handle_failed_authentication(email_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 was_authenticated_third_party:
            running_pipeline = pipeline.get(request)
            redirect_url = pipeline.get_complete_url(backend_name=running_pipeline['backend'])

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

        # Ensure that the external marketing site can
        # detect that the user is logged in.
        return set_logged_in_cookies(request, response, possibly_authenticated_user)
    except AuthFailedError as error:
        return JsonResponse(error.get_response())
Beispiel #23
0
def create_account(request, post_override=None):
    """
    Deprecated. Use RegistrationView instead.
    JSON call to create new edX account.
    Used by form in signup_modal.html, which is included into header.html
    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
    if not configuration_helpers.get_value(
            'ALLOW_PUBLIC_ACCOUNT_CREATION',
            settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)):
        return HttpResponseForbidden(_("Account creation not allowed."))

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        return HttpResponseForbidden(SYSTEM_MAINTENANCE_MSG)

    warnings.warn("Please use RegistrationView instead.", DeprecationWarning)

    try:
        user = create_account_with_params(request, post_override
                                          or request.POST)
    except AccountValidationError as exc:
        return JsonResponse(
            {
                'success': False,
                'value': text_type(exc),
                'field': exc.field
            },
            status=400)
    except ValidationError as exc:
        field, error_list = next(iteritems(exc.message_dict))
        return JsonResponse(
            {
                "success": False,
                "field": field,
                "value": ' '.join(error_list),
            },
            status=400)

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

    # Resume the third-party-auth pipeline if necessary.
    if third_party_auth.is_enabled() and pipeline.running(request):
        running_pipeline = pipeline.get(request)
        redirect_url = pipeline.get_complete_url(running_pipeline['backend'])

    response = JsonResponse({
        'success': True,
        'redirect_url': redirect_url,
    })
    set_logged_in_cookies(request, response, user)
    return response
Beispiel #24
0
    def post_account_consent(self, request, consent_provided):
        """
        Interpret the account-wide form above, and save it to a UserDataSharingConsentAudit object for later retrieval.
        """
        self.lift_quarantine(request)

        # Load the linked EnterpriseCustomer for this request.
        customer = get_enterprise_customer_for_request(request)
        if customer is None:
            # If we can't get an EnterpriseCustomer from the pipeline, then we don't really
            # have enough state to do anything meaningful. Just send the user to the login
            # screen; if they want to sign in with an Enterprise-linked SSO, they can do
            # so, and the pipeline will get them back here if they need to be.
            return redirect('signin_user')

        # Attempt to retrieve a user being manipulated by the third-party auth
        # pipeline. Return a 404 if no such user exists.
        social_auth = get_real_social_auth_object(request)
        user = getattr(social_auth, 'user', None)
        if user is None:
            raise Http404

        if not consent_provided and active_provider_enforces_data_sharing(
                request, EnterpriseCustomer.AT_LOGIN):
            # Flush the session to avoid the possibility of accidental login and to abort the pipeline.
            # pipeline is flushed only if data sharing is enforced, in other cases let the user to login.
            request.session.flush()
            failure_url = request.POST.get('failure_url') or reverse(
                'dashboard')
            return redirect(failure_url)

        ec_user, __ = EnterpriseCustomerUser.objects.get_or_create(
            user_id=user.id,
            enterprise_customer=customer,
        )

        UserDataSharingConsentAudit.objects.update_or_create(
            user=ec_user,
            defaults={
                'state':
                (UserDataSharingConsentAudit.ENABLED
                 if consent_provided else UserDataSharingConsentAudit.DISABLED)
            })

        # Resume auth pipeline
        backend_name = request.session.get('partial_pipeline',
                                           {}).get('backend')
        return redirect(get_complete_url(backend_name))
Beispiel #25
0
def create_account(request, post_override=None):
    """
    Deprecated. Use RegistrationView instead.
    JSON call to create new edX account.
    Used by form in signup_modal.html, which is included into header.html
    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
    if not configuration_helpers.get_value(
            'ALLOW_PUBLIC_ACCOUNT_CREATION',
            settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)
    ):
        return HttpResponseForbidden(_("Account creation not allowed."))

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        return HttpResponseForbidden(SYSTEM_MAINTENANCE_MSG)

    warnings.warn("Please use RegistrationView instead.", DeprecationWarning)

    try:
        user = create_account_with_params(request, post_override or request.POST)
    except AccountValidationError as exc:
        return JsonResponse({'success': False, 'value': text_type(exc), 'field': exc.field}, status=400)
    except ValidationError as exc:
        field, error_list = next(iteritems(exc.message_dict))
        return JsonResponse(
            {
                "success": False,
                "field": field,
                "value": ' '.join(error_list),
            },
            status=400
        )

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

    # Resume the third-party-auth pipeline if necessary.
    if third_party_auth.is_enabled() and pipeline.running(request):
        running_pipeline = pipeline.get(request)
        redirect_url = pipeline.get_complete_url(running_pipeline['backend'])

    response = JsonResponse({
        'success': True,
        'redirect_url': redirect_url,
    })
    set_logged_in_cookies(request, response, user)
    return response
Beispiel #26
0
    def post_account_consent(self, request, consent_provided):
        """
        Interpret the account-wide form above, and save it to a UserDataSharingConsentAudit object for later retrieval.
        """
        self.lift_quarantine(request)

        # Load the linked EnterpriseCustomer for this request. Return a 404 if no such EnterpriseCustomer exists
        customer = get_enterprise_customer_for_request(request)
        if customer is None:
            raise Http404

        # Attempt to retrieve a user being manipulated by the third-party auth
        # pipeline. Return a 404 if no such user exists.
        social_auth = get_real_social_auth_object(request)
        user = getattr(social_auth, 'user', None)
        if user is None:
            raise Http404

        if not consent_provided and active_provider_enforces_data_sharing(
                request, EnterpriseCustomer.AT_LOGIN):
            # Flush the session to avoid the possibility of accidental login and to abort the pipeline.
            # pipeline is flushed only if data sharing is enforced, in other cases let the user to login.
            request.session.flush()
            return redirect(reverse('dashboard'))

        ec_user, __ = EnterpriseCustomerUser.objects.get_or_create(
            user_id=user.id,
            enterprise_customer=customer,
        )

        UserDataSharingConsentAudit.objects.update_or_create(
            user=ec_user,
            defaults={
                'state':
                (UserDataSharingConsentAudit.ENABLED
                 if consent_provided else UserDataSharingConsentAudit.DISABLED)
            })

        # Resume auth pipeline
        backend_name = request.session.get('partial_pipeline',
                                           {}).get('backend')
        return redirect(get_complete_url(backend_name))
Beispiel #27
0
    def get_request_and_strategy(self, auth_entry=None, redirect_uri=None):
        """Gets a fully-configured request and strategy.

        These two objects contain circular references, so we create them
        together. The references themselves are a mixture of normal __init__
        stuff and monkey-patching done by python-social-auth. See, for example,
        social.apps.django_apps.utils.strategy().
        """
        request = self.request_factory.get(
            pipeline.get_complete_url(self.backend_name) +
            '?redirect_state=redirect_state_value&code=code_value&state=state_value')
        request.user = auth_models.AnonymousUser()
        request.session = cache.SessionStore()
        request.session[self.backend_name + '_state'] = 'state_value'

        if auth_entry:
            request.session[pipeline.AUTH_ENTRY_KEY] = auth_entry

        strategy = social_utils.load_strategy(backend=self.backend_name, redirect_uri=redirect_uri, request=request)
        request.social_strategy = strategy

        return request, strategy
Beispiel #28
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')
Beispiel #29
0
    def post(self, request):
        """
        Process the above form.
        """
        # Verify that all necessary resources are present
        verify_edx_resources()
        self.lift_quarantine(request)
        customer = get_enterprise_customer_for_request(request)
        if customer is None:
            raise Http404
        consent_provided = request.POST.get('data_sharing_consent', False)
        # If the checkbox is unchecked, no value will be sent
        user = get_real_social_auth_object(request).user
        ec_user, __ = EnterpriseCustomerUser.objects.get_or_create(
            user_id=user.id,
            enterprise_customer=customer,
        )

        UserDataSharingConsentAudit.objects.update_or_create(
            user=ec_user,
            defaults={
                'state':
                (UserDataSharingConsentAudit.ENABLED
                 if consent_provided else UserDataSharingConsentAudit.DISABLED)
            })
        if not consent_provided:
            # Flush the session to avoid the possibility of accidental login and to abort the pipeline.
            # pipeline is flushed only if data sharing is enforced, in other cases let the user to login.
            if active_provider_enforces_data_sharing(
                    request, EnterpriseCustomer.AT_LOGIN):
                request.session.flush()
                return redirect(reverse('dashboard'))

        # Resume auth pipeline
        backend_name = request.session.get('partial_pipeline',
                                           {}).get('backend')
        return redirect(get_complete_url(backend_name))
Beispiel #30
0
 def assert_json_success_response_looks_correct(self, response):
     """Asserts the json response indicates success and redirection."""
     self.assertEqual(200, response.status_code)
     payload = json.loads(response.content)
     self.assertTrue(payload.get('success'))
     self.assertEqual(pipeline.get_complete_url(self.provider.backend_name), payload.get('redirect_url'))
Beispiel #31
0
def login_user(request):
    """
    AJAX request to log in the user.
    """
    _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_metric('login_user_enrollment_action',
                      request.POST.get('enrollment_action'))
    set_custom_metric('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_metric('login_user_tpa_success', True)
            except AuthFailedError as e:
                set_custom_metric('login_user_tpa_success', False)
                set_custom_metric('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()
                response_content[
                    'error_code'] = 'third-party-auth-with-no-linked-account'
                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)
            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'])

        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_metric('login_user_auth_failed_error', False)
        set_custom_metric('login_user_response_status', response.status_code)
        return response
    except AuthFailedError as error:
        log.exception(error.get_response())
        # original code returned a 200 status code with status=False for errors. This flag
        # is used for rolling out a transition to using a 400 status code for errors, which
        # is a breaking-change, but will hopefully be a tolerable breaking-change.
        status = 400 if UPDATE_LOGIN_USER_ERROR_STATUS_CODE.is_enabled(
        ) else 200
        response = JsonResponse(error.get_response(), status=status)
        set_custom_metric('login_user_auth_failed_error', True)
        set_custom_metric('login_user_response_status', response.status_code)
        return response
Beispiel #32
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 #33
0
def login_user_custom(request, error=""):  # pylint: disable=too-many-statements,unused-argument
    """AJAX request to log in the user."""

    backend_name = None
    email = None
    password = None
    redirect_url = None
    response = None
    running_pipeline = None
    third_party_auth_requested = third_party_auth.is_enabled(
    ) and pipeline.running(request)
    third_party_auth_successful = False
    trumped_by_first_party_auth = bool(request.POST.get('email')) or bool(
        request.POST.get('password'))
    user = None
    platform_name = configuration_helpers.get_value("platform_name",
                                                    settings.PLATFORM_NAME)

    if third_party_auth_requested and not trumped_by_first_party_auth:
        # 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.
        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)

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

            return HttpResponse(message, content_type="text/plain", status=403)

    else:

        if 'email' not in request.POST or 'password' not in request.POST:
            return JsonResponse({
                "success":
                False,
                # TODO: User error message
                "value":
                _('There was an error receiving your login information. Please email us.'
                  ),
            })  # TODO: this should be status code 400

        email = request.POST['email']
        password = request.POST['password']
        try:
            user = User.objects.get(email=email)
        except User.DoesNotExist:
            if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
                AUDIT_LOG.warning(u"Login failed - Unknown user email")
            else:
                AUDIT_LOG.warning(
                    u"Login failed - Unknown user email: {0}".format(email))

    # check if the user has a linked shibboleth account, if so, redirect the user to shib-login
    # This behavior is pretty much like what gmail does for shibboleth.  Try entering some @stanford.edu
    # address into the Gmail login.
    if settings.FEATURES.get('AUTH_USE_SHIB') and user:
        try:
            eamap = ExternalAuthMap.objects.get(user=user)
            if eamap.external_domain.startswith(
                    openedx.core.djangoapps.external_auth.views.
                    SHIBBOLETH_DOMAIN_PREFIX):
                return JsonResponse({
                    "success": False,
                    "redirect": reverse('shib-login'),
                })  # TODO: this should be status code 301  # pylint: disable=fixme
        except ExternalAuthMap.DoesNotExist:
            # This is actually the common case, logging in user without external linked login
            AUDIT_LOG.info(u"User %s w/o external auth attempting login", user)

    # see if account has been locked out due to excessive login failures
    user_found_by_email_lookup = user
    if user_found_by_email_lookup and LoginFailures.is_feature_enabled():
        if LoginFailures.is_user_locked_out(user_found_by_email_lookup):
            lockout_message = _(
                'This account has been temporarily locked due '
                'to excessive login failures. Try again later.')
            return JsonResponse({
                "success": False,
                "value": lockout_message,
            })  # TODO: this should be status code 429  # pylint: disable=fixme

    # see if the user must reset his/her password due to any policy settings
    if user_found_by_email_lookup and PasswordHistory.should_user_reset_password_now(
            user_found_by_email_lookup):
        return JsonResponse({
            "success":
            False,
            "value":
            _('Your password has expired due to password policy on this account. You must '
              'reset your password before you can log in again. Please click the '
              '"Forgot Password" link on this page to reset your password before logging in again.'
              ),
        })  # TODO: this should be status code 403  # pylint: disable=fixme

    # if the user doesn't exist, we want to set the username to an invalid
    # username so that authentication is guaranteed to fail and we can take
    # advantage of the ratelimited backend
    username = user.username if user else ""

    if not third_party_auth_successful:
        try:
            user = authenticate(username=username,
                                password=password,
                                request=request)
        # this occurs when there are too many attempts from the same IP address
        except RateLimitException:
            return JsonResponse({
                "success":
                False,
                "value":
                _('Too many failed login attempts. Try again later.'),
            })  # TODO: this should be status code 429  # pylint: disable=fixme

    if user is None:
        # tick the failed login counters if the user exists in the database
        if user_found_by_email_lookup and LoginFailures.is_feature_enabled():
            LoginFailures.increment_lockout_counter(user_found_by_email_lookup)

        # if we didn't find this username earlier, the account for this email
        # doesn't exist, and doesn't have a corresponding password
        if username != "":
            if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
                loggable_id = user_found_by_email_lookup.id if user_found_by_email_lookup else "<unknown>"
                AUDIT_LOG.warning(
                    u"Login failed - password for user.id: {0} is invalid".
                    format(loggable_id))
            else:
                AUDIT_LOG.warning(
                    u"Login failed - password for {0} is invalid".format(
                        email))
        return JsonResponse({
            "success": False,
            "value": _('Email or password is incorrect.'),
        })  # TODO: this should be status code 400  # pylint: disable=fixme

    # successful login, clear failed login attempts counters, if applicable
    if LoginFailures.is_feature_enabled():
        LoginFailures.clear_lockout_counter(user)

    # Track the user's sign in
    if hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
        tracking_context = tracker.get_tracker().resolve_context()
        analytics.identify(
            user.id,
            {
                'email': email,
                'username': username
            },
            {
                # Disable MailChimp because we don't want to update the user's email
                # and username in MailChimp on every page load. We only need to capture
                # this data on registration/activation.
                'MailChimp': False
            })

        analytics.track(user.id,
                        "edx.bi.user.account.authenticated", {
                            'category': "conversion",
                            'label': request.POST.get('course_id'),
                            'provider': None
                        },
                        context={
                            'ip': tracking_context.get('ip'),
                            'Google Analytics': {
                                'clientId': tracking_context.get('client_id')
                            }
                        })

    if user is not None and user.is_active:
        try:
            # We do not log here, because we have a handler registered
            # to perform logging on successful logins.
            login(request, user)
            if request.POST.get('remember') == 'true':
                request.session.set_expiry(604800)
                log.debug("Setting user session to never expire")
            else:
                request.session.set_expiry(0)
        except Exception as exc:  # pylint: disable=broad-except
            AUDIT_LOG.critical(
                "Login failed - Could not create session. Is memcached running?"
            )
            log.critical(
                "Login failed - Could not create session. Is memcached running?"
            )
            log.exception(exc)
            raise

        redirect_url = None  # The AJAX method calling should know the default destination upon success
        if third_party_auth_successful:
            redirect_url = pipeline.get_complete_url(backend_name)

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

        # Ensure that the external marketing site can
        # detect that the user is logged in.
        return set_logged_in_cookies(request, response, user)

    if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
        AUDIT_LOG.warning(
            u"Login failed - Account not active for user.id: {0}, resending activation"
            .format(user.id))
    else:
        AUDIT_LOG.warning(
            u"Login failed - Account not active for user {0}, resending activation"
            .format(username))

    reactivation_email_for_user_custom(request, user)
    not_activated_msg = _(
        "Before you sign in, you need to activate your account. We have sent you an "
        "email message with instructions for activating your account.")
    return JsonResponse({
        "success": False,
        "value": not_activated_msg,
    })  # TODO: this should be status code 400  # pylint: disable=fixme
    def test_complete_url_returns_expected_format(self):
        complete_url = pipeline.get_complete_url(self.enabled_provider.backend_name)

        self.assertTrue(complete_url.startswith('/auth/complete'))
        self.assertIn(self.enabled_provider.backend_name, complete_url)
Beispiel #35
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 #36
0
        if settings.FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'):
            log.info('bypassing activation email')
            login_user.is_active = True
            login_user.save()
            AUDIT_LOG.info(
                u"Login activated on extauth account - {0} ({1})".format(
                    login_user.username, login_user.email))

    dog_stats_api.increment("common.student.account_created")
    redirect_url = try_change_enrollment(request)

    # Resume the third-party-auth pipeline if necessary.
    if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH') and pipeline.running(
            request):
        running_pipeline = pipeline.get(request)
        redirect_url = pipeline.get_complete_url(running_pipeline['backend'])

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

    # set the login cookie for the edx marketing site
    # we want this cookie to be accessed via javascript
    # so httponly is set to None

    if request.session.get_expire_at_browser_close():
        max_age = None
        expires = None
    else:
        max_age = request.session.get_expiry_age()
Beispiel #37
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 #38
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_metric('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_metric('login_user_tpa_success', True)
            except AuthFailedError as e:
                set_custom_metric('login_user_tpa_success', False)
                set_custom_metric('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()
                response_content[
                    'error_code'] = 'third-party-auth-with-no-linked-account'
                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'])

        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_metric('login_user_auth_failed_error', False)
        set_custom_metric('login_user_response_status', response.status_code)
        set_custom_metric('login_user_redirect_url', redirect_url)
        return response
    except AuthFailedError as error:
        log.exception(error.get_response())
        response = JsonResponse(error.get_response(), status=400)
        set_custom_metric('login_user_auth_failed_error', True)
        set_custom_metric('login_user_response_status', response.status_code)
        return response
Beispiel #39
0
    def post_account_consent(self, request, consent_provided):
        """
        Interpret the account-wide form above, and save it to a UserDataSharingConsentAudit object for later retrieval.
        """
        self.lift_quarantine(request)

        # Load the linked EnterpriseCustomer for this request.
        customer = get_enterprise_customer_for_request(request)
        if customer is None:
            # If we can't get an EnterpriseCustomer from the pipeline, then we don't really
            # have enough state to do anything meaningful. Just send the user to the login
            # screen; if they want to sign in with an Enterprise-linked SSO, they can do
            # so, and the pipeline will get them back here if they need to be.
            return redirect('signin_user')

        # Attempt to retrieve a user being manipulated by the third-party auth
        # pipeline. Return a 404 if no such user exists.
        social_auth = get_real_social_auth_object(request)
        user = getattr(social_auth, 'user', None)
        if user is None:
            raise Http404

        if not consent_provided and active_provider_enforces_data_sharing(
                request, EnterpriseCustomer.AT_LOGIN):
            # Flush the session to avoid the possibility of accidental login and to abort the pipeline.
            # pipeline is flushed only if data sharing is enforced, in other cases let the user to login.
            request.session.flush()
            failure_url = request.POST.get('failure_url') or reverse(
                'dashboard')
            return redirect(failure_url)

        enterprise_customer_user, __ = EnterpriseCustomerUser.objects.get_or_create(
            user_id=user.id,
            enterprise_customer=customer,
        )

        platform_name = configuration_helpers.get_value(
            'PLATFORM_NAME', settings.PLATFORM_NAME)
        messages.success(
            request,
            _('{span_start}Account created{span_end} Thank you for creating an account with {platform_name}.'
              ).format(
                  platform_name=platform_name,
                  span_start='<span>',
                  span_end='</span>',
              ))
        if not user.is_active:
            messages.info(
                request,
                _('{span_start}Activate your account{span_end} Check your inbox for an activation email. '
                  'You will not be able to log back into your account until you have activated it.'
                  ).format(span_start='<span>', span_end='</span>'))

        UserDataSharingConsentAudit.objects.update_or_create(
            user=enterprise_customer_user,
            defaults={
                'state':
                (UserDataSharingConsentAudit.ENABLED
                 if consent_provided else UserDataSharingConsentAudit.DISABLED)
            })

        # Resume auth pipeline
        backend_name = get_partial_pipeline(request).get('backend')
        return redirect(get_complete_url(backend_name))
Beispiel #40
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
    def test_complete_url_returns_expected_format(self):
        complete_url = pipeline.get_complete_url(
            self.enabled_provider.backend_name)

        self.assertTrue(complete_url.startswith('/auth/complete'))
        self.assertIn(self.enabled_provider.backend_name, complete_url)
Beispiel #42
0
def login_user(request):
    """
    AJAX request to log in the user.
    """
    post_data = request.POST.copy()

    # Decrypt form data if it is encrypted
    if 'data_token' in request.POST:
        data_token = request.POST.get('data_token')

        try:
            decoded_data = jwt.decode(
                data_token,
                settings.EDRAAK_LOGISTRATION_SECRET_KEY,
                verify=False,
                algorithms=[settings.EDRAAK_LOGISTRATION_SIGNING_ALGORITHM])
            post_data.update(decoded_data)

        except jwt.ExpiredSignatureError:
            err_msg = u"The provided data_token has been expired"
            AUDIT_LOG.warning(err_msg)

            return JsonResponse({
                "success": False,
                "value": err_msg,
            },
                                status=400)

        except jwt.DecodeError:
            err_msg = u"Signature verification failed"
            AUDIT_LOG.warning(err_msg)

            return JsonResponse({
                "success": False,
                "value": err_msg,
            },
                                status=400)

        except (jwt.InvalidTokenError, ValueError):
            err_msg = u"Invalid token"
            AUDIT_LOG.warning(err_msg)

            return JsonResponse({
                "success": False,
                "value": err_msg,
            },
                                status=400)

    third_party_auth_requested = third_party_auth.is_enabled(
    ) and pipeline.running(request)
    trumped_by_first_party_auth = bool(post_data.get('email')) or bool(
        post_data.get('password'))
    was_authenticated_third_party = False
    parent_user = None
    child_user = None

    try:
        if third_party_auth_requested and not trumped_by_first_party_auth:
            # 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:
                email_user = _do_third_party_auth(request)
                was_authenticated_third_party = True
            except AuthFailedError as e:
                return HttpResponse(e.value,
                                    content_type="text/plain",
                                    status=403)

        elif 'child_user_id' in post_data:
            child_user_id = post_data['child_user_id']
            try:
                child_user = User.objects.get(id=child_user_id)
            except User.DoesNotExist:
                if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
                    AUDIT_LOG.warning(
                        u"Child login failed - Unknown child user id")
                else:
                    AUDIT_LOG.warning(
                        u"Child login failed - Unknown child user id: {0}".
                        format(child_user_id))

        else:
            email_user = _get_user_by_email(request, post_data=post_data)

        if child_user:
            parent_user = request.user
            email_user = child_user

        _check_shib_redirect(email_user)
        _check_excessive_login_attempts(email_user)
        _check_forced_password_reset(email_user)

        # set the user object to child_user object if a child is being logged in
        possibly_authenticated_user = email_user

        if not was_authenticated_third_party:
            possibly_authenticated_user = _authenticate_first_party(
                request, post_data, email_user)
            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, post_data, possibly_authenticated_user)

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

        _handle_successful_authentication_and_login(
            possibly_authenticated_user, request, post_data)
        if parent_user:
            request.session['parent_user'] = json.dumps({
                'user_id':
                parent_user.id,
                'username':
                parent_user.username,
                'email':
                parent_user.email,
                'name':
                parent_user.profile.name
            })

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

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

        # Ensure that the external marketing site can
        # detect that the user is logged in.
        return set_logged_in_cookies(request, response,
                                     possibly_authenticated_user)
    except AuthFailedError as error:
        return JsonResponse(error.get_response())
Beispiel #43
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)