mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #29628 -- Made createsuperuser validate password against username and required fields.
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							f1fbef6cd1
						
					
				
				
					commit
					793e9bb35a
				
			| @@ -108,6 +108,11 @@ class Command(BaseCommand): | ||||
|                     if error_msg: | ||||
|                         raise CommandError(error_msg) | ||||
|  | ||||
|             user_data[self.UserModel.USERNAME_FIELD] = username | ||||
|             fake_user_data[self.UserModel.USERNAME_FIELD] = ( | ||||
|                 self.username_field.remote_field.model(username) if self.username_field.remote_field | ||||
|                 else username | ||||
|             ) | ||||
|             # Prompt for required fields. | ||||
|             for field_name in self.UserModel.REQUIRED_FIELDS: | ||||
|                 if not options['interactive']: | ||||
| @@ -134,28 +139,33 @@ class Command(BaseCommand): | ||||
|                         # Wrap any foreign keys in fake model instances | ||||
|                         if field.remote_field: | ||||
|                             fake_user_data[field_name] = field.remote_field.model(input_value) | ||||
|                     # Prompt for a password. | ||||
|                     while password 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.") | ||||
|             if options['interactive']: | ||||
|                 # Prompt for a password. | ||||
|                 while password 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: | ||||
|                         validate_password(password2, self.UserModel(**fake_user_data)) | ||||
|                     except exceptions.ValidationError as err: | ||||
|                         self.stderr.write('\n'.join(err.messages)) | ||||
|                         response = input('Bypass password validation and create user anyway? [y/N]: ') | ||||
|                         if response.lower() != 'y': | ||||
|                             password = None | ||||
|                             # Don't validate blank passwords. | ||||
|                             continue | ||||
|                         try: | ||||
|                             validate_password(password2, self.UserModel(**fake_user_data)) | ||||
|                         except exceptions.ValidationError as err: | ||||
|                             self.stderr.write('\n'.join(err.messages)) | ||||
|                             response = input('Bypass password validation and create user anyway? [y/N]: ') | ||||
|                             if response.lower() != 'y': | ||||
|                                 password = None | ||||
|             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.") | ||||
|         except KeyboardInterrupt: | ||||
|             self.stderr.write('\nOperation cancelled.') | ||||
|             sys.exit(1) | ||||
| @@ -168,13 +178,6 @@ class Command(BaseCommand): | ||||
|                 'to create one manually.' | ||||
|             ) | ||||
|  | ||||
|         if username: | ||||
|             user_data[self.UserModel.USERNAME_FIELD] = username | ||||
|             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.") | ||||
|  | ||||
|     def get_input_data(self, field, message, default=None): | ||||
|         """ | ||||
|         Override this method if you want to customize data inputs or | ||||
|   | ||||
| @@ -9,7 +9,7 @@ from django.db import models | ||||
| # that every user provide a date of birth. This lets us test | ||||
| # changes in username datatype, and non-text required fields. | ||||
| class CustomUserManager(BaseUserManager): | ||||
|     def create_user(self, email, date_of_birth, password=None): | ||||
|     def create_user(self, email, date_of_birth, password=None, **fields): | ||||
|         """ | ||||
|         Creates and saves a User with the given email and password. | ||||
|         """ | ||||
| @@ -19,14 +19,15 @@ class CustomUserManager(BaseUserManager): | ||||
|         user = self.model( | ||||
|             email=self.normalize_email(email), | ||||
|             date_of_birth=date_of_birth, | ||||
|             **fields | ||||
|         ) | ||||
|  | ||||
|         user.set_password(password) | ||||
|         user.save(using=self._db) | ||||
|         return user | ||||
|  | ||||
|     def create_superuser(self, email, password, date_of_birth): | ||||
|         u = self.create_user(email, password=password, date_of_birth=date_of_birth) | ||||
|     def create_superuser(self, email, password, date_of_birth, **fields): | ||||
|         u = self.create_user(email, password=password, date_of_birth=date_of_birth, **fields) | ||||
|         u.is_admin = True | ||||
|         u.save(using=self._db) | ||||
|         return u | ||||
| @@ -37,11 +38,12 @@ class CustomUser(AbstractBaseUser): | ||||
|     is_active = models.BooleanField(default=True) | ||||
|     is_admin = models.BooleanField(default=False) | ||||
|     date_of_birth = models.DateField() | ||||
|     first_name = models.CharField(max_length=50) | ||||
|  | ||||
|     custom_objects = CustomUserManager() | ||||
|  | ||||
|     USERNAME_FIELD = 'email' | ||||
|     REQUIRED_FIELDS = ['date_of_birth'] | ||||
|     REQUIRED_FIELDS = ['date_of_birth', 'first_name'] | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.email | ||||
|   | ||||
| @@ -29,6 +29,8 @@ MOCK_INPUT_KEY_TO_PROMPTS = { | ||||
|     # @mock_inputs dict key: [expected prompt messages], | ||||
|     'bypass': ['Bypass password validation and create user anyway? [y/N]: '], | ||||
|     'email': ['Email address: '], | ||||
|     'date_of_birth': ['Date of birth: '], | ||||
|     'first_name': ['First name: '], | ||||
|     'username': ['Username: ', lambda: "Username (leave blank to use '%s'): " % get_default_username()], | ||||
| } | ||||
|  | ||||
| @@ -327,6 +329,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase): | ||||
|             interactive=False, | ||||
|             email="joe@somewhere.org", | ||||
|             date_of_birth="1976-04-01", | ||||
|             first_name='Joe', | ||||
|             stdout=new_io, | ||||
|         ) | ||||
|         command_output = new_io.getvalue().strip() | ||||
| @@ -552,6 +555,85 @@ class CreatesuperuserManagementCommandTestCase(TestCase): | ||||
|  | ||||
|         test(self) | ||||
|  | ||||
|     @override_settings(AUTH_PASSWORD_VALIDATORS=[ | ||||
|         {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, | ||||
|     ]) | ||||
|     def test_validate_password_against_username(self): | ||||
|         new_io = StringIO() | ||||
|         username = 'supremelycomplex' | ||||
|  | ||||
|         def bad_then_good_password(index=[0]): | ||||
|             """Return username the first two times, then a valid password.""" | ||||
|             index[0] += 1 | ||||
|             if index[0] <= 2: | ||||
|                 return username | ||||
|             return 'superduperunguessablepassword' | ||||
|  | ||||
|         @mock_inputs({ | ||||
|             'password': bad_then_good_password, | ||||
|             'username': username, | ||||
|             'email': '', | ||||
|             'bypass': 'n', | ||||
|         }) | ||||
|         def test(self): | ||||
|             call_command( | ||||
|                 'createsuperuser', | ||||
|                 interactive=True, | ||||
|                 stdin=MockTTY(), | ||||
|                 stdout=new_io, | ||||
|                 stderr=new_io, | ||||
|             ) | ||||
|             self.assertEqual( | ||||
|                 new_io.getvalue().strip(), | ||||
|                 'The password is too similar to the username.\n' | ||||
|                 'Superuser created successfully.' | ||||
|             ) | ||||
|  | ||||
|         test(self) | ||||
|  | ||||
|     @override_settings( | ||||
|         AUTH_USER_MODEL='auth_tests.CustomUser', | ||||
|         AUTH_PASSWORD_VALIDATORS=[ | ||||
|             {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, | ||||
|         ] | ||||
|     ) | ||||
|     def test_validate_password_against_required_fields(self): | ||||
|         new_io = StringIO() | ||||
|         username = 'josephine' | ||||
|  | ||||
|         # Returns the username the first two times it's called, then a valid | ||||
|         # password. | ||||
|         def bad_then_good_password(index=[0]): | ||||
|             """Return username the first two times, then a valid password.""" | ||||
|             index[0] += 1 | ||||
|             if index[0] <= 2: | ||||
|                 return username | ||||
|             return 'superduperunguessablepassword' | ||||
|  | ||||
|         @mock_inputs({ | ||||
|             'password': bad_then_good_password, | ||||
|             'username': username, | ||||
|             'first_name': 'josephine', | ||||
|             'date_of_birth': '1970-01-01', | ||||
|             'email': 'joey@example.com', | ||||
|             'bypass': 'n', | ||||
|         }) | ||||
|         def test(self): | ||||
|             call_command( | ||||
|                 'createsuperuser', | ||||
|                 interactive=True, | ||||
|                 stdin=MockTTY(), | ||||
|                 stdout=new_io, | ||||
|                 stderr=new_io, | ||||
|             ) | ||||
|             self.assertEqual( | ||||
|                 new_io.getvalue().strip(), | ||||
|                 "The password is too similar to the first name.\n" | ||||
|                 "Superuser created successfully." | ||||
|             ) | ||||
|  | ||||
|         test(self) | ||||
|  | ||||
|     def test_blank_username(self): | ||||
|         """Creation fails if --username is blank.""" | ||||
|         new_io = StringIO() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user