def dispatch_to_register(): """Force user creation on login or register""" request = strategy.request data['terms_of_service'] = True data['honor_code'] = True data['password'] = make_random_password() # force name creation if it is empty in sso-profile data['name'] = ' '.join([data['firstname'], data['lastname'] ]).strip() or data['username'] data['provider'] = backend.name if request.session.get('ExternalAuthMap'): del request.session['ExternalAuthMap'] try: user = User.objects.get(email=data['email']) except User.DoesNotExist: try: create_account_with_params(request, data) except AccountValidationError: data['username'] = data['email'] create_account_with_params(request, data) user = request.user user.is_active = True user.set_unusable_password() user.save() del data['password'] return {'user': user}
def dispatch_to_register(): """Force user creation on login or register""" request = strategy.request data['terms_of_service'] = "True" data['honor_code'] = 'True' data['password'] = make_random_password() data['provider'] = backend.name if request.session.get('ExternalAuthMap'): del request.session['ExternalAuthMap'] try: user = User.objects.get(email=data['email']) except User.DoesNotExist: create_account_with_params(request, data) user = request.user user.first_name = data['first_name'] user.last_name = data['last_name'] user.is_active = True user.save() CourseCreator.objects.get_or_create( user=user, state=CourseCreator.UNREQUESTED) return {'user': user}
def get(self, request): response = super(RegistrationApiView, self).get(request) registration_form = json.loads(response.content) for field_name in FORUS_SPECIFIC_FIELDS: registration_form['fields'].append({ 'name': field_name, # Make it ran 'required': True, 'errorMessages': {}, 'restrictions': {}, 'form': 'register', 'placeholder': '', }) registration_form['submit_url'] = reverse('forus_v1_reg_api') for field in registration_form['fields']: field_name = field['name'] if field_name in HIDDEN_FIELDS: field.update({ 'instructions': '', 'label': '', 'requiredStr': '*', 'type': 'hidden', }) if 'password' == field_name: field['defaultValue'] = pipeline.make_random_password() return JsonResponse(registration_form)
def test_pseudorandomly_picks_chars_from_charset(self): random_instance = random.Random(self.seed) expected = ''.join( random_instance.choice(pipeline._PASSWORD_CHARSET) for _ in xrange(pipeline._DEFAULT_RANDOM_PASSWORD_LENGTH)) random_instance.seed(self.seed) self.assertEqual(expected, pipeline.make_random_password(choice_fn=random_instance.choice))
def dispatch_to_register(): """Force user creation on login or register""" request = strategy.request data['terms_of_service'] = True data['honor_code'] = True data['password'] = make_random_password() # force name creation if it is empty in sso-profile data['name'] = ' '.join([data.get('firstname', ''), data.get('lastname', '')]).strip() or data['username'] data['provider'] = backend.name if request.session.get('ExternalAuthMap'): del request.session['ExternalAuthMap'] try: user = User.objects.get(email=data['email']) except User.DoesNotExist: create_account_with_params(request, data) user = request.user user.first_name = data.get('firstname') user.last_name = data.get('lastname') user.is_active = True user.save() return {'user': user}
def create_user_account(request, params): params = dict(params.items()) params["password"] = pipeline.make_random_password() form = AccountCreationForm( data=params, enforce_username_neq_password=True, enforce_password_policy=False, tos_required=False ) with transaction.commit_on_success(): # first, create the account (user, profile, registration) = _do_create_account(form) uid = params["uid"] # associate the user with azuread associate_user(request.backend, uid, user) # Perform operations that are non-critical parts of account creation preferences_api.set_user_preference(user, LANGUAGE_KEY, get_language()) # create_comments_service_user(user) registration.activate() return {"username": user.username, "email": user.email}
def dispatch_to_register(): """Force user creation on login or register""" request = strategy.request data['terms_of_service'] = "True" data['honor_code'] = 'True' data['password'] = make_random_password() data['provider'] = backend.name if request.session.get('ExternalAuthMap'): del request.session['ExternalAuthMap'] try: user = User.objects.get(email=data['email']) except User.DoesNotExist: create_account_with_params(request, data) user = request.user user.first_name = data['first_name'] user.last_name = data['last_name'] user.is_active = True user.save() CourseCreator.objects.get_or_create( user=user, state=CourseCreator.UNREQUESTED ) return {'user': user}
def change_user_data(self, data): from third_party_auth.pipeline import make_random_password firstname = data.get('Firstname', '') lastname = data.get('Lastname', '') email = data.get('Email', '') username = re.sub('[\W_]', '', email) name = ' '.join([firstname, lastname]).strip() or username return { 'email': email, 'firstname': firstname, 'lastname': lastname, 'username': username, 'name': name, 'password': make_random_password() }
def dispatch_to_register(): """Force user creation on login or register""" request = strategy.request data["terms_of_service"] = True data["honor_code"] = True data["password"] = make_random_password() # force name creation if it is empty in sso-profile data["name"] = " ".join([data.get("firstname", ""), data.get("lastname", "")]).strip() or data["username"] data["provider"] = backend.name if request.session.get("ExternalAuthMap"): del request.session["ExternalAuthMap"] try: user = User.objects.get(email=data["email"]) except User.DoesNotExist: create_account_with_params(request, data) user = request.user user.is_active = True user.save() return {"user": user}
def test_probably_only_uses_charset(self): # This is ultimately probablistic since we could randomly select a good character 100000 consecutive times. for char in pipeline.make_random_password(length=100000): self.assertIn(char, pipeline._PASSWORD_CHARSET)
def test_default_args(self): self.assertEqual(pipeline._DEFAULT_RANDOM_PASSWORD_LENGTH, len(pipeline.make_random_password()))
def create_account_with_params_custom(request, params): """ Given a request and a dict of parameters (which may or may not have come from the request), create an account for the requesting user, including creating a comments service user object and sending an activation email. This also takes external/third-party auth into account, updates that as necessary, and authenticates the user for the request's session. Does not return anything. Raises AccountValidationError if an account with the username or email specified by params already exists, or ValidationError if any of the given parameters is invalid for any other reason. Issues with this code: * It is not transactional. If there is a failure part-way, an incomplete account will be created and left in the database. * Third-party auth passwords are not verified. There is a comment that they are unused, but it would be helpful to have a sanity check that they are sane. * It is over 300 lines long (!) and includes disprate functionality, from registration e-mails to all sorts of other things. It should be broken up into semantically meaningful functions. * The user-facing text is rather unfriendly (e.g. "Username must be a minimum of two characters long" rather than "Please use a username of at least two characters"). """ # Copy params so we can modify it; we can't just do dict(params) because if # params is request.POST, that results in a dict containing lists of values params = dict(params.items()) # allow to define custom set of required/optional/hidden fields via configuration extra_fields = configuration_helpers.get_value( 'REGISTRATION_EXTRA_FIELDS', getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {})) # Boolean of whether a 3rd party auth provider and credentials were provided in # the API so the newly created account can link with the 3rd party account. # # Note: this is orthogonal to the 3rd party authentication pipeline that occurs # when the account is created via the browser and redirect URLs. should_link_with_social_auth = third_party_auth.is_enabled( ) and 'provider' in params if should_link_with_social_auth or (third_party_auth.is_enabled() and pipeline.running(request)): params["password"] = pipeline.make_random_password() # Add a form requirement for data sharing consent if the EnterpriseCustomer # for the request requires it at login extra_fields[ 'data_sharing_consent'] = data_sharing_consent_requirement_at_login( request) # if doing signup for an external authorization, then get email, password, name from the eamap # don't use the ones from the form, since the user could have hacked those # unless originally we didn't get a valid email or name from the external auth # TODO: We do not check whether these values meet all necessary criteria, such as email length do_external_auth = 'ExternalAuthMap' in request.session if do_external_auth: eamap = request.session['ExternalAuthMap'] try: validate_email(eamap.external_email) params["email"] = eamap.external_email except ValidationError: pass if eamap.external_name.strip() != '': params["name"] = eamap.external_name params["password"] = eamap.internal_password log.debug(u'In create_account with external_auth: user = %s, email=%s', params["name"], params["email"]) extended_profile_fields = configuration_helpers.get_value( 'extended_profile_fields', []) enforce_password_policy = (settings.FEATURES.get( "ENFORCE_PASSWORD_POLICY", False) and not do_external_auth) # Can't have terms of service for certain SHIB users, like at Stanford registration_fields = getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {}) tos_required = (registration_fields.get('terms_of_service') != 'hidden' or registration_fields.get('honor_code') != 'hidden') and ( not settings.FEATURES.get("AUTH_USE_SHIB") or not settings.FEATURES.get("SHIB_DISABLE_TOS") or not do_external_auth or not eamap.external_domain.startswith( openedx.core.djangoapps.external_auth.views. SHIBBOLETH_DOMAIN_PREFIX)) params['name'] = "{} {}".format(params.get('first_name'), params.get('last_name')) form = AccountCreationForm( data=params, extra_fields=extra_fields, extended_profile_fields=extended_profile_fields, enforce_username_neq_password=True, enforce_password_policy=enforce_password_policy, tos_required=tos_required, ) custom_form = get_registration_extension_form(data=params) # Perform operations within a transaction that are critical to account creation with transaction.atomic(): # first, create the account (user, profile, registration) = _do_create_account_custom(form, custom_form) # next, link the account with social auth, if provided via the API. # (If the user is using the normal register page, the social auth pipeline does the linking, not this code) if should_link_with_social_auth: backend_name = params['provider'] request.social_strategy = social_utils.load_strategy(request) redirect_uri = reverse('social:complete', args=(backend_name, )) request.backend = social_utils.load_backend( request.social_strategy, backend_name, redirect_uri) social_access_token = params.get('access_token') if not social_access_token: raise ValidationError({ 'access_token': [ _("An access_token is required when passing value ({}) for provider." ).format(params['provider']) ] }) request.session[ pipeline.AUTH_ENTRY_KEY] = pipeline.AUTH_ENTRY_REGISTER_API pipeline_user = None error_message = "" try: pipeline_user = request.backend.do_auth(social_access_token, user=user) except AuthAlreadyAssociated: error_message = _( "The provided access_token is already associated with another user." ) except (HTTPError, AuthException): error_message = _("The provided access_token is not valid.") if not pipeline_user or not isinstance(pipeline_user, User): # Ensure user does not re-enter the pipeline request.social_strategy.clean_partial_pipeline() raise ValidationError({'access_token': [error_message]}) # Perform operations that are non-critical parts of account creation preferences_api.set_user_preference(user, LANGUAGE_KEY, get_language()) if settings.FEATURES.get('ENABLE_DISCUSSION_EMAIL_DIGEST'): try: enable_notifications(user) except Exception: # pylint: disable=broad-except log.exception( "Enable discussion notifications failed for user {id}.".format( id=user.id)) dog_stats_api.increment("common.student.account_created") # If the user is registering via 3rd party auth, track which provider they use third_party_provider = None running_pipeline = None if third_party_auth.is_enabled() and pipeline.running(request): running_pipeline = pipeline.get(request) third_party_provider = provider.Registry.get_from_pipeline( running_pipeline) # Store received data sharing consent field values in the pipeline for use # by any downstream pipeline elements which require them. running_pipeline['kwargs'][ 'data_sharing_consent'] = form.cleaned_data.get( 'data_sharing_consent', None) # Track the user's registration if hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY: tracking_context = tracker.get_tracker().resolve_context() identity_args = [ user.id, # pylint: disable=no-member { 'email': user.email, 'username': user.username, 'name': profile.name, # Mailchimp requires the age & yearOfBirth to be integers, we send a sane integer default if falsey. 'age': profile.age or -1, 'yearOfBirth': profile.year_of_birth or datetime.datetime.now(UTC).year, 'education': profile.level_of_education_display, 'address': profile.mailing_address, 'gender': profile.gender_display, 'country': unicode(profile.country), } ] if hasattr(settings, 'MAILCHIMP_NEW_USER_LIST_ID'): identity_args.append( {"MailChimp": { "listId": settings.MAILCHIMP_NEW_USER_LIST_ID }}) analytics.identify(*identity_args) analytics.track( user.id, "edx.bi.user.account.registered", { 'category': 'conversion', 'label': params.get('course_id'), 'provider': third_party_provider.name if third_party_provider else None }, context={ 'ip': tracking_context.get('ip'), 'Google Analytics': { 'clientId': tracking_context.get('client_id') } }) # Announce registration REGISTER_USER.send(sender=None, user=user, profile=profile) create_comments_service_user(user) # Don't send email if we are: # # 1. Doing load testing. # 2. Random user generation for other forms of testing. # 3. External auth bypassing activation. # 4. Have the platform configured to not require e-mail activation. # 5. Registering a new user using a trusted third party provider (with skip_email_verification=True) # # Note that this feature is only tested as a flag set one way or # the other for *new* systems. we need to be careful about # changing settings on a running system to make sure no users are # left in an inconsistent state (or doing a migration if they are). send_email = ( not settings.FEATURES.get('SKIP_EMAIL_VALIDATION', None) and not settings.FEATURES.get('AUTOMATIC_AUTH_FOR_TESTING') and not (do_external_auth and settings.FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH')) and not (third_party_provider and third_party_provider.skip_email_verification and user.email == running_pipeline['kwargs'].get('details', {}).get('email'))) if send_email: send_account_activation_email(request, registration, user) else: registration.activate() _enroll_user_in_pending_courses( user) # Enroll student in any pending courses # Immediately after a user creates an account, we log them in. They are only # logged in until they close the browser. They can't log in again until they click # the activation link from the email. new_user = authenticate(username=user.username, password=params['password']) login(request, new_user) request.session.set_expiry(0) try: record_registration_attributions(request, new_user) # Don't prevent a user from registering due to attribution errors. except Exception: # pylint: disable=broad-except log.exception('Error while attributing cookies to user registration.') # TODO: there is no error checking here to see that the user actually logged in successfully, # and is not yet an active user. if new_user is not None: AUDIT_LOG.info(u"Login success on new account creation - {0}".format( new_user.username)) if do_external_auth: eamap.user = new_user eamap.dtsignup = datetime.datetime.now(UTC) eamap.save() AUDIT_LOG.info(u"User registered with external_auth %s", new_user.username) AUDIT_LOG.info(u'Updated ExternalAuthMap for %s to be %s', new_user.username, eamap) if settings.FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'): log.info('bypassing activation email') new_user.is_active = True new_user.save() AUDIT_LOG.info( u"Login activated on extauth account - {0} ({1})".format( new_user.username, new_user.email)) return new_user
def ensure_user_information( strategy, auth_entry, backend=None, user=None, social=None, allow_inactive_user=False, *args, **kwargs ): """ Ensure that we have the necessary information about a user to proceed with the pipeline. Either an existing account or registration data. """ data = {} try: if 'data' in kwargs['response']: user_data = kwargs['response']['data'][0] else: user_data = kwargs['response'] log.info('Get user data: %s', str(user_data)) access_token = kwargs['response']['access_token'] country = user_data.get('country') if not country: log.info('No country in response.') # Received fields could be pretty different from the expected, mandatory are only 'username' and 'email' data['username'] = user_data.get('username', user_data.get('name')) data['first_name'] = user_data.get('firstName', user_data.get('first_name')) data['last_name'] = user_data.get('lastName', user_data.get('last_name')) data['email'] = user_data.get('email') data['country'] = country data['access_token'] = access_token if any((data['first_name'], data['last_name'])): data['name'] = '{} {}'.format(['first_name'], data['last_name']).strip() else: data['name'] = user_data.get('username') if not all((data['username'], data['email'])): raise AuthEntryError( backend, "One of the required parameters (username or email) is not received with the user data." ) except AuthEntryError as e: log.exception(e) raise except Exception as e: log.exception(e) raise AuthEntryError(backend, "Cannot receive user's data") if not user: request = strategy.request data['terms_of_service'] = "True" data['honor_code'] = 'True' data['password'] = make_random_password() data['provider'] = backend.name try: user = User.objects.get(email=data['email']) except User.DoesNotExist: create_account_with_params(request, data) user.is_active = True user.save() return {'user': user}
def create_account( request, post_override=None): # pylint: disable-msg=too-many-statements """ JSON call to create new edX account. Used by form in signup_modal.html, which is included into navigation.html """ js = {'success': False} # pylint: disable-msg=invalid-name post_vars = post_override if post_override else request.POST # allow for microsites to define their own set of required/optional/hidden fields extra_fields = microsite.get_value( 'REGISTRATION_EXTRA_FIELDS', getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {})) if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH') and pipeline.running( request): post_vars = dict(post_vars.items()) post_vars.update({'password': pipeline.make_random_password()}) # if doing signup for an external authorization, then get email, password, name from the eamap # don't use the ones from the form, since the user could have hacked those # unless originally we didn't get a valid email or name from the external auth DoExternalAuth = 'ExternalAuthMap' in request.session if DoExternalAuth: eamap = request.session['ExternalAuthMap'] try: validate_email(eamap.external_email) email = eamap.external_email except ValidationError: email = post_vars.get('email', '') if eamap.external_name.strip() == '': name = post_vars.get('name', '') else: name = eamap.external_name password = eamap.internal_password post_vars = dict(post_vars.items()) post_vars.update(dict(email=email, name=name, password=password)) log.debug(u'In create_account with external_auth: user = %s, email=%s', name, email) # Confirm we have a properly formed request for a in ['username', 'email', 'password', 'name']: if a not in post_vars: js['value'] = _("Error (401 {field}). E-mail us.").format(field=a) js['field'] = a return JsonResponse(js, status=400) if extra_fields.get('honor_code', 'required') == 'required' and \ post_vars.get('honor_code', 'false') != u'true': js['value'] = _("To enroll, you must follow the honor code.").format( field=a) js['field'] = 'honor_code' return JsonResponse(js, status=400) # Can't have terms of service for certain SHIB users, like at Stanford tos_required = (not settings.FEATURES.get("AUTH_USE_SHIB") or not settings.FEATURES.get("SHIB_DISABLE_TOS") or not DoExternalAuth or not eamap.external_domain.startswith( external_auth.views.SHIBBOLETH_DOMAIN_PREFIX)) if tos_required: if post_vars.get('terms_of_service', 'false') != u'true': js['value'] = _("You must accept the terms of service.").format( field=a) js['field'] = 'terms_of_service' return JsonResponse(js, status=400) # Confirm appropriate fields are there. # TODO: Check e-mail format is correct. # TODO: Confirm e-mail is not from a generic domain (mailinator, etc.)? Not sure if # this is a good idea # TODO: Check password is sane required_post_vars = ['username', 'email', 'name', 'password'] required_post_vars += [ fieldname for fieldname, val in extra_fields.items() if val == 'required' ] if tos_required: required_post_vars.append('terms_of_service') for field_name in required_post_vars: if field_name in ('gender', 'level_of_education'): min_length = 1 else: min_length = 2 if field_name not in post_vars or len( post_vars[field_name]) < min_length: error_str = { 'username': _('Username must be minimum of two characters long'), 'email': _('A properly formatted e-mail is required'), 'name': _('Your legal name must be a minimum of two characters long'), 'password': _('A valid password is required'), 'terms_of_service': _('Accepting Terms of Service is required'), 'honor_code': _('Agreeing to the Honor Code is required'), 'level_of_education': _('A level of education is required'), 'gender': _('Your gender is required'), 'year_of_birth': _('Your year of birth is required'), 'mailing_address': _('Your mailing address is required'), 'goals': _('A description of your goals is required'), 'city': _('A city is required'), 'country': _('A country is required') } if field_name in error_str: js['value'] = error_str[field_name] else: js['value'] = _('You are missing one or more required fields') js['field'] = field_name return JsonResponse(js, status=400) max_length = 75 if field_name == 'username': max_length = 30 if field_name in ('email', 'username') and len( post_vars[field_name]) > max_length: error_str = { 'username': _('Username cannot be more than {0} characters long').format( max_length), 'email': _('Email cannot be more than {0} characters long').format( max_length) } js['value'] = error_str[field_name] js['field'] = field_name return JsonResponse(js, status=400) try: validate_email(post_vars['email']) email_domain = post_vars['email'].split('@')[-1] white_list_domain = settings.FEATURES.get('WHITE_LIST_DOMAIN', []) mth = [x for x in white_list_domain if re.search(x, email_domain)] if not mth: raise ValidationError(_(u'Enter a valid e-mail.'), code='invalid') except ValidationError: js['value'] = _("Valid e-mail is required.").format(field=a) js['field'] = 'email' return JsonResponse(js, status=400) try: validate_slug(post_vars['username']) except ValidationError: js['value'] = _( "Username should only consist of A-Z and 0-9, with no spaces." ).format(field=a) js['field'] = 'username' return JsonResponse(js, status=400) # enforce password complexity as an optional feature # but not if we're doing ext auth b/c those pws never get used and are auto-generated so might not pass validation if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False) and not DoExternalAuth: try: password = post_vars['password'] validate_password_length(password) validate_password_complexity(password) validate_password_dictionary(password) except ValidationError, err: js['value'] = _('Password: '******'; '.join(err.messages) js['field'] = 'password' return JsonResponse(js, status=400)