def test_username_to_hash_is_normalized(salt_list): """ Make sure identical usernames with different cases map to the same retired username. """ username_mixed = 'ALearnerUserName' username_lower = username_mixed.lower() retired_username_mixed = user_util.get_retired_username(username_mixed, salt_list) retired_username_lower = user_util.get_retired_username(username_lower, salt_list) # No matter the case of the input username, the retired username hash should be identical. assert retired_username_mixed == retired_username_lower
def test_successful_rehash(retire_user_forums, capsys): """ Run the command with users of all different hash statuses, expect success """ users_skipped, users_needing_rehash, retirements = _setup_users() call_command('bulk_rehash_retired_usernames') output = capsys.readouterr().out # Make sure forums was called the correct number of times assert retire_user_forums.call_count == 2 for user in users_skipped: assert "User ID {} because the hash would not change.".format( user.id) in output expected_username_calls = [] for user in users_needing_rehash: retirement = retirements[user.id] user.refresh_from_db() retirement.refresh_from_db() new_retired_username = get_retired_username( retirement.original_username, settings.RETIRED_USER_SALTS, settings.RETIRED_USERNAME_FMT) expected_username_calls.append(call(new_retired_username)) assert "User ID {} to rehash their retired username".format( user.id) in output assert new_retired_username == user.username assert new_retired_username == retirement.retired_username retire_user_forums.assert_has_calls(expected_username_calls)
def test_unicode_username_to_hash(): username = u'ÁĹéáŕńéŕŰśéŕŃáḿéẂíthŰńíćődé' retired_username = user_util.get_retired_username( username, VALID_SALT_LIST_ONE_SALT) assert retired_username != username # Since SHA1 is used, the hexadecimal digest length should be 40. assert len(retired_username.split('_')[-1]) == 40
def test_username_to_hash(salt_list): username = '******' retired_username = user_util.get_retired_username(username, salt_list) assert retired_username != username assert retired_username.startswith('_'.join(user_util.RETIRED_USERNAME_DEFAULT_FMT.split('_')[0:-1])) # Since SHA1 is used, the hexadecimal digest length should be 40. assert len(retired_username.split('_')[-1]) == 40
def test_username_to_hash_with_different_format(salt_list): username = '******' retired_username_fmt = "{}_is_now_the_retired_username" retired_username = user_util.get_retired_username(username, salt_list, retired_username_fmt=retired_username_fmt) assert retired_username.endswith('_'.join(retired_username_fmt.split('_')[1:])) # Since SHA1 is used, the hexadecimal digest length should be 40. assert len(retired_username.split('_')[0]) == 40
def test_correct_username_hash(salt_list): """ Verify that get_retired_username uses the current salt and returns the expected hash. """ username = '******' # Valid retired usernames for the above username when using VALID_SALT_LIST_THREE_SALTS. valid_retired_usernames = [ user_util.RETIRED_USERNAME_DEFAULT_FMT.format(user_util._compute_retired_hash(username.lower(), salt)) for salt in salt_list ] retired_username = user_util.get_retired_username(username, salt_list) assert retired_username == valid_retired_usernames[-1]
def test_forums_failed(retire_user_forums, capsys): """ Run the command with users of all different hash statuses, expect success """ users_skipped, users_faked, users_needing_rehash, retirements = _setup_users( ) retire_user_forums.side_effect = Exception( 'something bad happened with forums') call_command('bulk_rehash_retired_usernames') output = capsys.readouterr().out # Make sure forums was called the correct number of times assert retire_user_forums.call_count == 2 for user in users_skipped: assert "User ID {} because the user does not appear to have a retired username:"******"User ID {} because the hash would not change.".format( user.id) in output expected_username_calls = [] for user in users_needing_rehash: retirement = retirements[user.id] user.refresh_from_db() retirement.refresh_from_db() new_retired_username = get_retired_username( retirement.original_username, settings.RETIRED_USER_SALTS, settings.RETIRED_USERNAME_FMT) expected_username_calls.append(call(new_retired_username)) assert "User ID {} to rehash their retired username".format( user.id) in output # Confirm that the usernames are *not* updated, due to the forums error assert new_retired_username != user.username assert new_retired_username != retirement.retired_username assert "FAILED! 2 retirements failed to rehash. Retirement IDs:" in output retire_user_forums.assert_has_calls(expected_username_calls)
def test_forums_404(retire_user_forums, capsys): """ Run the command with users of all different hash statuses, expect success """ users_skipped, users_needing_rehash, retirements = _setup_users() retire_user_forums.side_effect = comment_client.utils.CommentClientRequestError( 'not found', status_codes=404) call_command('bulk_rehash_retired_usernames') output = capsys.readouterr().out # Make sure forums was called the correct number of times assert retire_user_forums.call_count == 2 for user in users_skipped: assert "User ID {} because the hash would not change.".format( user.id) in output expected_username_calls = [] for user in users_needing_rehash: retirement = retirements[user.id] user.refresh_from_db() retirement.refresh_from_db() new_retired_username = get_retired_username( retirement.original_username, settings.RETIRED_USER_SALTS, settings.RETIRED_USERNAME_FMT) expected_username_calls.append(call(new_retired_username)) assert "User ID {} to rehash their retired username".format( user.id) in output # Confirm that the usernames *are* updated, since this is a non-blocking forums error assert new_retired_username == user.username assert new_retired_username == retirement.retired_username assert "Success!" in output retire_user_forums.assert_has_calls(expected_username_calls)
def handle(self, *args, **options): """ Execute the command. """ dry_run = options['dry_run'] retirements = UserRetirementStatus.objects.all().select_related('user') failed_retirements = [] for retirement in retirements: original_username = retirement.original_username old_retired_username = retirement.retired_username new_retired_username = user_util.get_retired_username( original_username, settings.RETIRED_USER_SALTS, settings.RETIRED_USERNAME_FMT) # If the original username was already normalized (or all lowercase), the old and new hashes would # match: if old_retired_username == new_retired_username: print( u'Skipping UserRetirementStatus ID {} / User ID {} because the hash would not change.' .format( retirement.id, retirement.user.id, )) # Found an username to update else: print(u'Updating UserRetirementStatus ID {} / User ID {} ' u'to rehash their retired username: {} -> {}'.format( retirement.id, retirement.user.id, old_retired_username, new_retired_username)) if not dry_run: try: # Update the forums first, that way if it fails the user can # be re-run. It does not need to be in the same transaction, # as the local db updates and can be slow, so keeping it # outside to cut down on potential deadlocks. cc_user = comment_client.User.from_django_user( retirement.user) # The user may not exist in forums, if it doesn't that's not # an error. try: cc_user.retire(new_retired_username) except comment_client.utils.CommentClientRequestError as e: if e.status_code != 404: print( u'UserRetirementStatus ID {} User ID {} failed to rename in forums: {}' .format(retirement.id, retirement.user.id, text_type(e))) raise # Update and save both the user table and retirement queue table: with transaction.atomic(): # Only rename them in auth_user if they've already been retired if retirement.user.username == old_retired_username: retirement.user.username = new_retired_username retirement.user.save() retirement.retired_username = new_retired_username retirement.save() except Exception as exc: # pylint: disable=broad-except print( u'UserRetirementStatus ID {} User ID {} failed rename' .format(retirement.id, retirement.user.id)) print(text_type(exc)) failed_retirements.append(retirement) if failed_retirements: print( '------------------------------------------------------------') print( u'FAILED! {} retirements failed to rehash. Retirement IDs:\n{}' .format( len(failed_retirements), '\n'.join([text_type(r.id) for r in failed_retirements]))) else: print(u'Success! {} retirements examined.'.format( len(retirements)))
def handle(self, *args, **options): """ Execute the command. """ dry_run = options['dry_run'] retirements = UserRetirementStatus.objects.all().select_related('user') failed_retirements = [] for retirement in retirements: original_username = retirement.original_username old_retired_username = retirement.retired_username new_retired_username = user_util.get_retired_username( original_username, settings.RETIRED_USER_SALTS, settings.RETIRED_USERNAME_FMT ) # If the original username was already normalized (or all lowercase), the old and new hashes would # match: if old_retired_username == new_retired_username: print( 'Skipping UserRetirementStatus ID {} / User ID {} because the hash would not change.'.format( retirement.id, retirement.user.id, ) ) # Found an username to update else: print( 'Updating UserRetirementStatus ID {} / User ID {} ' 'to rehash their retired username: {} -> {}'.format( retirement.id, retirement.user.id, old_retired_username, new_retired_username ) ) if not dry_run: try: # Update the forums first, that way if it fails the user can # be re-run. It does not need to be in the same transaction, # as the local db updates and can be slow, so keeping it # outside to cut down on potential deadlocks. cc_user = comment_client.User.from_django_user(retirement.user) # The user may not exist in forums, if it doesn't that's not # an error. try: cc_user.retire(new_retired_username) except comment_client.utils.CommentClientRequestError as e: if e.status_code != 404: print( 'UserRetirementStatus ID {} User ID {} failed to rename in forums: {}'.format( retirement.id, retirement.user.id, text_type(e) ) ) raise # Update and save both the user table and retirement queue table: with transaction.atomic(): # Only rename them in auth_user if they've already been retired if retirement.user.username == old_retired_username: retirement.user.username = new_retired_username retirement.user.save() retirement.retired_username = new_retired_username retirement.save() except Exception as exc: # pylint: disable=broad-except print( 'UserRetirementStatus ID {} User ID {} failed rename'.format( retirement.id, retirement.user.id ) ) print(text_type(exc)) failed_retirements.append(retirement) if failed_retirements: print('------------------------------------------------------------') print( 'FAILED! {} retirements failed to rehash. Retirement IDs:\n{}'.format( len(failed_retirements), '\n'.join([text_type(r.id) for r in failed_retirements]) ) ) else: print('Success! {} retirements examined.'.format(len(retirements)))
def test_username_to_hash_bad_salt(salt): """ Salts that are *not* lists/tuples should fail. """ with pytest.raises((ValueError, IndexError)): _ = user_util.get_retired_username('AnotherLearnerUserName', salt)