From 8b43e9b1af37ab222cbe2964ae13a9203afb9c92 Mon Sep 17 00:00:00 2001 From: Josh Schneier Date: Sat, 4 Aug 2018 18:16:47 -0400 Subject: [PATCH] Fixed #29616 -- Fixed createsuperuser for user models that don't have a password field. --- .../management/commands/createsuperuser.py | 23 ++++++++----- tests/auth_tests/models/__init__.py | 3 +- tests/auth_tests/models/no_password.py | 21 ++++++++++++ tests/auth_tests/test_management.py | 34 +++++++++++++++++++ 4 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 tests/auth_tests/models/no_password.py diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index 3a76bce90a6..d700ce4c0ee 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -17,6 +17,9 @@ class NotRunningInTTYException(Exception): pass +PASSWORD_FIELD = 'password' + + class Command(BaseCommand): help = 'Used to create a superuser.' requires_migrations_checks = True @@ -60,11 +63,15 @@ class Command(BaseCommand): def handle(self, *args, **options): username = options[self.UserModel.USERNAME_FIELD] database = options['database'] - - # If not provided, create the user with an unusable password - password = None user_data = {} verbose_field_name = self.username_field.verbose_name + try: + self.UserModel._meta.get_field(PASSWORD_FIELD) + except exceptions.FieldDoesNotExist: + pass + else: + # If not provided, create the user with an unusable password. + user_data[PASSWORD_FIELD] = None try: if options['interactive']: # Same as user_data but with foreign keys as fake model @@ -109,18 +116,16 @@ class Command(BaseCommand): if field.remote_field: fake_user_data[field_name] = field.remote_field.model(input_value) - # Prompt for a password. - while password is None: + # Prompt for a password if the model has one. + while PASSWORD_FIELD in user_data and user_data[PASSWORD_FIELD] is None: password = getpass.getpass() password2 = getpass.getpass('Password (again): ') if password != password2: self.stderr.write("Error: Your passwords didn't match.") - password = None # Don't validate passwords that don't match. continue if password.strip() == '': self.stderr.write("Error: Blank passwords aren't allowed.") - password = None # Don't validate blank passwords. continue try: @@ -129,7 +134,8 @@ class Command(BaseCommand): self.stderr.write('\n'.join(err.messages)) response = input('Bypass password validation and create user anyway? [y/N]: ') if response.lower() != 'y': - password = None + continue + user_data[PASSWORD_FIELD] = password else: # Non-interactive mode. if username is None: @@ -147,7 +153,6 @@ class Command(BaseCommand): else: raise CommandError('You must use --%s with --noinput.' % field_name) - user_data['password'] = password self.UserModel._default_manager.db_manager(database).create_superuser(**user_data) if options['verbosity'] >= 1: self.stdout.write("Superuser created successfully.") diff --git a/tests/auth_tests/models/__init__.py b/tests/auth_tests/models/__init__.py index e5e38a1e2e7..3422255d332 100644 --- a/tests/auth_tests/models/__init__.py +++ b/tests/auth_tests/models/__init__.py @@ -5,6 +5,7 @@ from .custom_user import ( from .invalid_models import CustomUserNonUniqueUsername from .is_active import IsActiveTestUser1 from .minimal import MinimalUser +from .no_password import NoPasswordUser from .uuid_pk import UUIDUser from .with_foreign_key import CustomUserWithFK, Email from .with_integer_username import IntegerUsernameUser @@ -13,6 +14,6 @@ from .with_last_login_attr import UserWithDisabledLastLoginField __all__ = ( 'CustomUser', 'CustomUserWithoutIsActiveField', 'CustomPermissionsUser', 'CustomUserWithFK', 'Email', 'ExtensionUser', 'IsActiveTestUser1', - 'MinimalUser', 'UUIDUser', 'CustomUserNonUniqueUsername', + 'MinimalUser', 'NoPasswordUser', 'UUIDUser', 'CustomUserNonUniqueUsername', 'IntegerUsernameUser', 'UserWithDisabledLastLoginField', ) diff --git a/tests/auth_tests/models/no_password.py b/tests/auth_tests/models/no_password.py new file mode 100644 index 00000000000..c7c40f76f63 --- /dev/null +++ b/tests/auth_tests/models/no_password.py @@ -0,0 +1,21 @@ +from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager +from django.db import models + + +class UserManager(BaseUserManager): + def _create_user(self, username, **extra_fields): + user = self.model(username=username, **extra_fields) + user.save(using=self._db) + return user + + def create_superuser(self, username=None, **extra_fields): + return self._create_user(username, **extra_fields) + + +class NoPasswordUser(AbstractBaseUser): + password = None + last_login = None + username = models.CharField(max_length=50, unique=True) + + USERNAME_FIELD = 'username' + objects = UserManager() diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py index 0c9ad81f3fa..1d340c56f09 100644 --- a/tests/auth_tests/test_management.py +++ b/tests/auth_tests/test_management.py @@ -879,6 +879,40 @@ class CreatesuperuserManagementCommandTestCase(TestCase): test(self) + @override_settings(AUTH_USER_MODEL='auth_tests.NoPasswordUser') + def test_usermodel_without_password(self): + new_io = StringIO() + + def test(self): + call_command( + 'createsuperuser', + interactive=False, + stdin=MockTTY(), + stdout=new_io, + stderr=new_io, + username='username', + ) + self.assertEqual(new_io.getvalue().strip(), 'Superuser created successfully.') + + test(self) + + @override_settings(AUTH_USER_MODEL='auth_tests.NoPasswordUser') + def test_usermodel_without_password_interactive(self): + new_io = StringIO() + + @mock_inputs({'username': 'username'}) + def test(self): + call_command( + 'createsuperuser', + interactive=True, + stdin=MockTTY(), + stdout=new_io, + stderr=new_io, + ) + self.assertEqual(new_io.getvalue().strip(), 'Superuser created successfully.') + + test(self) + class MultiDBCreatesuperuserTestCase(TestCase): multi_db = True