def test_user_exists(self): """ Verify that user_exists function returns correct response. """ # Create users from factory UserFactory(username='******', email='*****@*****.**') assert user_exists({'username': '******', 'email': '*****@*****.**'}) assert user_exists({'username': '******'}) assert user_exists({'email': '*****@*****.**'}) assert not user_exists({'username': '******'}) assert user_exists({'username': '******'})
def ensure_user_information( strategy, auth_entry, backend=None, user=None, social=None, current_partial=None, # lint-amnesty, pylint: disable=keyword-arg-before-vararg allow_inactive_user=False, details=None, *args, **kwargs): """ Ensure that we have the necessary information about a user (either an existing account or registration data) to proceed with the pipeline. """ # We're deliberately verbose here to make it clear what the intended # dispatch behavior is for the various pipeline entry points, given the # current state of the pipeline. Keep in mind the pipeline is re-entrant # and values will change on repeated invocations (for example, the first # time through the login flow the user will be None so we dispatch to the # login form; the second time it will have a value so we continue to the # next pipeline step directly). # # It is important that we always execute the entire pipeline. Even if # behavior appears correct without executing a step, it means important # invariants have been violated and future misbehavior is likely. def dispatch_to_login(): """Redirects to the login page.""" return redirect(AUTH_DISPATCH_URLS[AUTH_ENTRY_LOGIN]) def dispatch_to_register(): """Redirects to the registration page.""" return redirect(AUTH_DISPATCH_URLS[AUTH_ENTRY_REGISTER]) def should_force_account_creation(): """ For some third party providers, we auto-create user accounts """ current_provider = provider.Registry.get_from_pipeline({ 'backend': current_partial.backend, 'kwargs': kwargs }) return (current_provider and (current_provider.skip_email_verification or current_provider.send_to_registration_first)) def is_provider_saml(): """ Verify that the third party provider uses SAML """ current_provider = provider.Registry.get_from_pipeline({ 'backend': current_partial.backend, 'kwargs': kwargs }) saml_providers_list = list( provider.Registry.get_enabled_by_backend_name('tpa-saml')) return (current_provider and current_provider.slug in [ saml_provider.slug for saml_provider in saml_providers_list ]) if current_partial: strategy.session_set('partial_pipeline_token_', current_partial.token) strategy.storage.partial.store(current_partial) if not user: # Use only email for user existence check in case of saml provider if is_provider_saml(): user_details = {'email': details.get('email')} if details else None else: user_details = details if user_exists(user_details or {}): # User has not already authenticated and the details sent over from # identity provider belong to an existing user. return dispatch_to_login() if is_api(auth_entry): return HttpResponseBadRequest() elif auth_entry == AUTH_ENTRY_LOGIN: # User has authenticated with the third party provider but we don't know which edX # account corresponds to them yet, if any. if should_force_account_creation(): return dispatch_to_register() return dispatch_to_login() elif auth_entry == AUTH_ENTRY_REGISTER: # User has authenticated with the third party provider and now wants to finish # creating their edX account. return dispatch_to_register() elif auth_entry == AUTH_ENTRY_ACCOUNT_SETTINGS: raise AuthEntryError( backend, 'auth_entry is wrong. Settings requires a user.') elif auth_entry in AUTH_ENTRY_CUSTOM: # Pass the username, email, etc. via query params to the custom entry page: return redirect_to_custom_form(strategy.request, auth_entry, details or {}, kwargs) else: raise AuthEntryError(backend, 'auth_entry invalid') if not user.is_active: # The user account has not been verified yet. if allow_inactive_user: # This parameter is used by the auth_exchange app, which always allows users to # login, whether or not their account is validated. pass elif social is None: # The user has just registered a new account as part of this pipeline. Their account # is inactive but we allow the login to continue, because if we pause again to force # the user to activate their account via email, the pipeline may get lost (e.g. # email takes too long to arrive, user opens the activation email on a different # device, etc.). This is consistent with first party auth and ensures that the # pipeline completes fully, which is critical. pass else: # This is an existing account, linked to a third party provider but not activated. # Double-check these criteria: assert user is not None assert social is not None # We now also allow them to login again, because if they had entered their email # incorrectly then there would be no way for them to recover the account, nor # register anew via SSO. See SOL-1324 in JIRA. # However, we will log a warning for this case: logger.warning( '[THIRD_PARTY_AUTH] User is using third_party_auth to login but has not yet activated their account. ' 'Username: {username}'.format(username=user.username))
def get_username(strategy, details, backend, user=None, *args, **kwargs): # lint-amnesty, pylint: disable=keyword-arg-before-vararg """ Copy of social_core.pipeline.user.get_username to achieve 1. additional logging 2. case insensitive username checks 3. enforce same maximum and minimum length restrictions we have in `user_api/accounts` """ if 'username' not in backend.setting('USER_FIELDS', USER_FIELDS): return storage = strategy.storage if not user: email_as_username = strategy.setting('USERNAME_IS_FULL_EMAIL', False) uuid_length = strategy.setting('UUID_LENGTH', 16) min_length = strategy.setting('USERNAME_MIN_LENGTH', accounts.USERNAME_MIN_LENGTH) max_length = strategy.setting('USERNAME_MAX_LENGTH', accounts.USERNAME_MAX_LENGTH) do_slugify = strategy.setting('SLUGIFY_USERNAMES', False) do_clean = strategy.setting('CLEAN_USERNAMES', True) if do_clean: override_clean = strategy.setting('CLEAN_USERNAME_FUNCTION') if override_clean: clean_func = module_member(override_clean) else: clean_func = storage.user.clean_username else: clean_func = lambda val: val if do_slugify: override_slug = strategy.setting('SLUGIFY_FUNCTION') if override_slug: slug_func = module_member(override_slug) else: slug_func = slugify else: slug_func = lambda val: val if email_as_username and details.get('email'): username = details['email'] elif details.get('username'): username = details['username'] else: username = uuid4().hex short_username = (username[:max_length - uuid_length] if max_length is not None else username) final_username = slug_func(clean_func(username[:max_length])) # Generate a unique username for current user using username # as base but adding a unique hash at the end. Original # username is cut to avoid any field max_length. # The final_username may be empty and will skip the loop. # We are using our own version of user_exists to avoid possible case sensitivity issues. while not final_username or len( final_username) < min_length or user_exists( {'username': final_username}): # adding a dash between user-supplied and system-generated values to avoid weird combinations username = short_username + '-' + username_suffix_generator( uuid_length) final_username = slug_func(clean_func(username[:max_length])) logger.info( '[THIRD_PARTY_AUTH] New username generated. Username: {username}' .format(username=final_username)) else: final_username = storage.user.get_username(user) return {'username': final_username}