def post(self, request): """Create the user's account. You must send all required form fields with the request. You can optionally send a "course_id" param to indicate in analytics events that the user registered while enrolling in a particular course. Arguments: request (HTTPRequest) Returns: HttpResponse: 200 on success HttpResponse: 400 if the request is not valid. HttpResponse: 409 if an account with the given username or email address already exists HttpResponse: 403 operation not allowed """ should_be_rate_limited = getattr(request, 'limited', False) if should_be_rate_limited: return JsonResponse({'error_code': 'forbidden-request'}, status=403) if is_require_third_party_auth_enabled( ) and not pipeline.running(request): # if request is not running a third-party auth pipeline return HttpResponseForbidden( "Third party authentication is required to register. Username and password were received instead." ) data = request.POST.copy() self._handle_terms_of_service(data) response = self._handle_duplicate_email_username(request, data) if response: return response response, user = self._create_account(request, data) if response: return response redirect_to, root_url = get_next_url_for_login_page(request, include_host=True) redirect_url = get_redirect_url_with_host(root_url, redirect_to) response = self._create_response(request, {}, status_code=200, redirect_url=redirect_url) set_logged_in_cookies(request, response, user) if not user.is_active and settings.SHOW_ACCOUNT_ACTIVATION_CTA and not settings.MARKETING_EMAILS_OPT_IN: response.set_cookie( settings.SHOW_ACTIVATE_CTA_POPUP_COOKIE_NAME, True, domain=settings.SESSION_COOKIE_DOMAIN, path='/', secure=request.is_secure() ) # setting the cookie to show account activation dialogue in platform and learning MFE mark_user_change_as_expected(response, user.id) return response
def post(self, request): """Create the user's account. You must send all required form fields with the request. You can optionally send a "course_id" param to indicate in analytics events that the user registered while enrolling in a particular course. Arguments: request (HTTPRequest) Returns: HttpResponse: 200 on success HttpResponse: 400 if the request is not valid. HttpResponse: 409 if an account with the given username or email address already exists HttpResponse: 403 operation not allowed """ should_be_rate_limited = getattr(request, 'limited', False) if should_be_rate_limited: return JsonResponse({'error_code': 'forbidden-request'}, status=403) if is_require_third_party_auth_enabled( ) and not pipeline.running(request): # if request is not running a third-party auth pipeline return HttpResponseForbidden( "Third party authentication is required to register. Username and password were received instead." ) data = request.POST.copy() self._handle_terms_of_service(data) response = self._handle_duplicate_email_username(request, data) if response: return response response, user = self._create_account(request, data) if response: return response redirect_to, root_url = get_next_url_for_login_page(request, include_host=True) redirect_url = get_redirect_url_with_host(root_url, redirect_to) response = self._create_response(request, {}, status_code=200, redirect_url=redirect_url) set_logged_in_cookies(request, response, user) return response
def associate_by_email_if_oauth(auth_entry, backend, details, user, strategy, *args, **kwargs): """ This pipeline step associates the current social auth with the user with the same email address in the database. It defers to the social library's associate_by_email implementation, which verifies that only a single database user is associated with the email. This association is done ONLY if the user entered the pipeline belongs to Oauth provider and `ENABLE_REQUIRE_THIRD_PARTY_AUTH` is enabled. """ if is_require_third_party_auth_enabled() and is_oauth_provider(backend.name, **kwargs): association_response, user_is_active = get_associated_user_by_email_response( backend, details, user, *args, **kwargs) if user_is_active: return association_response
def is_registration_api_functional(): """ Checks if User Registration API "/user_api/v1/account/registration/" is functional or not depending upon two environment variables "settings.FEATURES['ALLOW_PUBLIC_ACCOUNT_CREATION']" and "settings.ENABLE_REQUIRE_THIRD_PARTY_AUTH" Arguments: None Returns: Boolean - True if User Registration API "/user_api/v1/account/registration/" is functional """ if (settings.FEATURES["ALLOW_PUBLIC_ACCOUNT_CREATION"] and not is_require_third_party_auth_enabled()): return True return False
def login_and_registration_form(request, initial_mode="login"): """Render the combined login/registration form, defaulting to login This relies on the JS to asynchronously load the actual form from the user_api. Keyword Args: initial_mode (string): Either "login" or "register". """ # Determine the URL to redirect to following login/registration/third_party_auth redirect_to = get_next_url_for_login_page(request) # If we're already logged in, redirect to the dashboard # Note: We check for the existence of login-related cookies in addition to is_authenticated # since Django's SessionAuthentication middleware auto-updates session cookies but not # the other login-related cookies. See ARCH-282. if request.user.is_authenticated and are_logged_in_cookies_set(request): return redirect(redirect_to) # Retrieve the form descriptions from the user API form_descriptions = _get_form_descriptions(request) # Our ?next= URL may itself contain a parameter 'tpa_hint=x' that we need to check. # If present, we display a login page focused on third-party auth with that provider. third_party_auth_hint = None if '?' in redirect_to: # lint-amnesty, pylint: disable=too-many-nested-blocks try: next_args = six.moves.urllib.parse.parse_qs(six.moves.urllib.parse.urlparse(redirect_to).query) if 'tpa_hint' in next_args: provider_id = next_args['tpa_hint'][0] tpa_hint_provider = third_party_auth.provider.Registry.get(provider_id=provider_id) if tpa_hint_provider: if tpa_hint_provider.skip_hinted_login_dialog: # Forward the user directly to the provider's login URL when the provider is configured # to skip the dialog. if initial_mode == "register": auth_entry = pipeline.AUTH_ENTRY_REGISTER else: auth_entry = pipeline.AUTH_ENTRY_LOGIN return redirect( pipeline.get_login_url(provider_id, auth_entry, redirect_url=redirect_to) ) third_party_auth_hint = provider_id initial_mode = "hinted_login" except (KeyError, ValueError, IndexError) as ex: log.exception(u"Unknown tpa_hint provider: %s", ex) enterprise_customer = enterprise_customer_for_request(request) # Redirect to authn MFE if it is enabled if should_redirect_to_authn_microfrontend() and not enterprise_customer: # This is to handle a case where a logged-in cookie is not present but the user is authenticated. # Note: If we don't handle this learner is redirected to authn MFE and then back to dashboard # instead of the desired redirect URL (e.g. finish_auth) resulting in learners not enrolling # into the courses. if request.user.is_authenticated and redirect_to: return redirect(redirect_to) query_params = request.GET.urlencode() url_path = '/{}{}'.format( initial_mode, '?' + query_params if query_params else '' ) return redirect(settings.AUTHN_MICROFRONTEND_URL + url_path) # Account activation message account_activation_messages = [ { 'message': message.message, 'tags': message.tags } for message in messages.get_messages(request) if 'account-activation' in message.tags ] account_recovery_messages = [ { 'message': message.message, 'tags': message.tags } for message in messages.get_messages(request) if 'account-recovery' in message.tags ] # Otherwise, render the combined login/registration page context = { 'data': { 'login_redirect_url': redirect_to, 'initial_mode': initial_mode, 'third_party_auth': third_party_auth_context(request, redirect_to, third_party_auth_hint), 'third_party_auth_hint': third_party_auth_hint or '', 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'support_link': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK), 'password_reset_support_link': configuration_helpers.get_value( 'PASSWORD_RESET_SUPPORT_LINK', settings.PASSWORD_RESET_SUPPORT_LINK ) or settings.SUPPORT_SITE_LINK, 'account_activation_messages': account_activation_messages, 'account_recovery_messages': account_recovery_messages, # Include form descriptions retrieved from the user API. # We could have the JS client make these requests directly, # but we include them in the initial page load to avoid # the additional round-trip to the server. 'login_form_desc': json.loads(form_descriptions['login']), 'registration_form_desc': json.loads(form_descriptions['registration']), 'password_reset_form_desc': json.loads(form_descriptions['password_reset']), 'account_creation_allowed': configuration_helpers.get_value( 'ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)), 'is_account_recovery_feature_enabled': is_secondary_email_feature_enabled(), 'is_multiple_user_enterprises_feature_enabled': is_multiple_user_enterprises_feature_enabled(), 'enterprise_slug_login_url': get_enterprise_slug_login_url(), 'is_require_third_party_auth_enabled': is_require_third_party_auth_enabled(), }, 'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in header 'responsive': True, 'allow_iframing': True, 'disable_courseware_js': True, 'combined_login_and_register': True, 'disable_footer': not configuration_helpers.get_value( 'ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER', settings.FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER'] ), } update_logistration_context_for_enterprise(request, context, enterprise_customer) response = render_to_response('student_account/login_and_register.html', context) handle_enterprise_cookies_for_logistration(request, response, context) return response
def login_user(request, api_version='v1'): """ 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 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) check_pwned_password_and_send_track_event.delay( user.id, request.POST.get('password'), user.is_staff) 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) _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)) 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(response, 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 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