From 8755fb15494b587436dc7a144417b45b2ea16355 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 9 Oct 2010 03:34:08 +0000 Subject: [PATCH] Fixed #14354 -- Normalized the handling of empty/null passwords in contrib.auth. This also updates the createsuperuser command to be more testable, and migrates some auth doctests. Thanks to berryp for the report, and Laurent Luce for the patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@14053 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + .../management/commands/createsuperuser.py | 15 +- django/contrib/auth/models.py | 25 +-- django/contrib/auth/tests/__init__.py | 3 +- django/contrib/auth/tests/basic.py | 153 ++++++++++-------- 5 files changed, 109 insertions(+), 88 deletions(-) diff --git a/AUTHORS b/AUTHORS index 62be95935a..129fce0941 100644 --- a/AUTHORS +++ b/AUTHORS @@ -310,6 +310,7 @@ answer newbie questions, and generally made Django that much better: Simon Litchfield Daniel Lindsley Trey Long + Laurent Luce Martin Mahner Matt McClanahan Stanislaus Madueke diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index aad6489b38..87751527c8 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -41,7 +41,8 @@ class Command(BaseCommand): username = options.get('username', None) email = options.get('email', None) interactive = options.get('interactive') - + verbosity = int(options.get('verbosity', 1)) + # Do quick and dirty validation if --noinput if not interactive: if not username or not email: @@ -79,7 +80,7 @@ class Command(BaseCommand): # try/except to trap for a keyboard interrupt and exit gracefully. if interactive: try: - + # Get a username while 1: if not username: @@ -100,7 +101,7 @@ class Command(BaseCommand): else: sys.stderr.write("Error: That username is already taken.\n") username = None - + # Get an email while 1: if not email: @@ -112,7 +113,7 @@ class Command(BaseCommand): email = None else: break - + # Get a password while 1: if not password: @@ -130,6 +131,8 @@ class Command(BaseCommand): except KeyboardInterrupt: sys.stderr.write("\nOperation cancelled.\n") sys.exit(1) - + User.objects.create_superuser(username, email, password) - print "Superuser created successfully." + if verbosity >= 1: + self.stdout.write("Superuser created successfully.\n") + diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 681cf1422a..3d927d813e 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -106,7 +106,6 @@ class UserManager(models.Manager): """ Creates and saves a User with the given username, e-mail and password. """ - now = datetime.datetime.now() # Normalize the address by lowercasing the domain part of the email @@ -122,10 +121,7 @@ class UserManager(models.Manager): is_active=True, is_superuser=False, last_login=now, date_joined=now) - if password: - user.set_password(password) - else: - user.set_unusable_password() + user.set_password(password) user.save(using=self._db) return user @@ -238,11 +234,14 @@ class User(models.Model): return full_name.strip() def set_password(self, raw_password): - import random - algo = 'sha1' - salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5] - hsh = get_hexdigest(algo, salt, raw_password) - self.password = '%s$%s$%s' % (algo, salt, hsh) + if raw_password is None: + self.set_unusable_password() + else: + import random + algo = 'sha1' + salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5] + hsh = get_hexdigest(algo, salt, raw_password) + self.password = '%s$%s$%s' % (algo, salt, hsh) def check_password(self, raw_password): """ @@ -265,7 +264,11 @@ class User(models.Model): self.password = UNUSABLE_PASSWORD def has_usable_password(self): - return self.password != UNUSABLE_PASSWORD + if self.password is None \ + or self.password == UNUSABLE_PASSWORD: + return False + else: + return True def get_group_permissions(self, obj=None): """ diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py index a1d02b6014..bc67d16d86 100644 --- a/django/contrib/auth/tests/__init__.py +++ b/django/contrib/auth/tests/__init__.py @@ -1,5 +1,5 @@ from django.contrib.auth.tests.auth_backends import BackendTest, RowlevelBackendTest, AnonymousUserBackendTest, NoAnonymousUserBackendTest -from django.contrib.auth.tests.basic import BASIC_TESTS +from django.contrib.auth.tests.basic import BasicTestCase from django.contrib.auth.tests.decorators import LoginRequiredTestCase from django.contrib.auth.tests.forms import UserCreationFormTest, AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest, UserChangeFormTest, PasswordResetFormTest from django.contrib.auth.tests.remote_user \ @@ -12,6 +12,5 @@ from django.contrib.auth.tests.views \ # The password for the fixture data users is 'password' __test__ = { - 'BASIC_TESTS': BASIC_TESTS, 'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS, } diff --git a/django/contrib/auth/tests/basic.py b/django/contrib/auth/tests/basic.py index ffa11d5d0e..7493dc68da 100644 --- a/django/contrib/auth/tests/basic.py +++ b/django/contrib/auth/tests/basic.py @@ -1,77 +1,92 @@ +from django.test import TestCase +from django.contrib.auth.models import User, AnonymousUser +from django.core.management import call_command +from StringIO import StringIO -BASIC_TESTS = """ ->>> from django.contrib.auth.models import User, AnonymousUser ->>> u = User.objects.create_user('testuser', 'test@example.com', 'testpw') ->>> u.has_usable_password() -True ->>> u.check_password('bad') -False ->>> u.check_password('testpw') -True ->>> u.set_unusable_password() ->>> u.save() ->>> u.check_password('testpw') -False ->>> u.has_usable_password() -False ->>> u2 = User.objects.create_user('testuser2', 'test2@example.com') ->>> u2.has_usable_password() -False +class BasicTestCase(TestCase): + def test_user(self): + "Check that users can be created and can set their password" + u = User.objects.create_user('testuser', 'test@example.com', 'testpw') + self.assertTrue(u.has_usable_password()) + self.assertFalse(u.check_password('bad')) + self.assertTrue(u.check_password('testpw')) ->>> u.is_authenticated() -True ->>> u.is_staff -False ->>> u.is_active -True ->>> u.is_superuser -False + # Check we can manually set an unusable password + u.set_unusable_password() + u.save() + self.assertFalse(u.check_password('testpw')) + self.assertFalse(u.has_usable_password()) + u.set_password('testpw') + self.assertTrue(u.check_password('testpw')) + u.set_password(None) + self.assertFalse(u.has_usable_password()) ->>> a = AnonymousUser() ->>> a.is_authenticated() -False ->>> a.is_staff -False ->>> a.is_active -False ->>> a.is_superuser -False ->>> a.groups.all() -[] ->>> a.user_permissions.all() -[] + # Check authentication/permissions + self.assertTrue(u.is_authenticated()) + self.assertFalse(u.is_staff) + self.assertTrue(u.is_active) + self.assertFalse(u.is_superuser) -# superuser tests. ->>> super = User.objects.create_superuser('super', 'super@example.com', 'super') ->>> super.is_superuser -True ->>> super.is_active -True ->>> super.is_staff -True + # Check API-based user creation with no password + u2 = User.objects.create_user('testuser2', 'test2@example.com') + self.assertFalse(u.has_usable_password()) -# -# Tests for createsuperuser management command. -# It's nearly impossible to test the interactive mode -- a command test helper -# would be needed (and *awesome*) -- so just test the non-interactive mode. -# This covers most of the important validation, but not all. -# ->>> from django.core.management import call_command + def test_anonymous_user(self): + "Check the properties of the anonymous user" + a = AnonymousUser() + self.assertFalse(a.is_authenticated()) + self.assertFalse(a.is_staff) + self.assertFalse(a.is_active) + self.assertFalse(a.is_superuser) + self.assertEqual(a.groups.all().count(), 0) + self.assertEqual(a.user_permissions.all().count(), 0) ->>> call_command("createsuperuser", interactive=False, username="joe", email="joe@somewhere.org") -Superuser created successfully. + def test_superuser(self): + "Check the creation and properties of a superuser" + super = User.objects.create_superuser('super', 'super@example.com', 'super') + self.assertTrue(super.is_superuser) + self.assertTrue(super.is_active) + self.assertTrue(super.is_staff) ->>> u = User.objects.get(username="joe") ->>> u.email -u'joe@somewhere.org' ->>> u.password -u'!' ->>> call_command("createsuperuser", interactive=False, username="joe+admin@somewhere.org", email="joe@somewhere.org") -Superuser created successfully. + def test_createsuperuser_management_command(self): + "Check the operation of the createsuperuser management command" + # We can use the management command to create a superuser + new_io = StringIO() + call_command("createsuperuser", + interactive=False, + username="joe", + email="joe@somewhere.org", + stdout=new_io + ) + command_output = new_io.getvalue().strip() + self.assertEqual(command_output, 'Superuser created successfully.') + u = User.objects.get(username="joe") + self.assertEquals(u.email, 'joe@somewhere.org') + self.assertTrue(u.check_password('')) + + # We can supress output on the management command + new_io = StringIO() + call_command("createsuperuser", + interactive=False, + username="joe2", + email="joe2@somewhere.org", + verbosity=0, + stdout=new_io + ) + command_output = new_io.getvalue().strip() + self.assertEqual(command_output, '') + u = User.objects.get(username="joe2") + self.assertEquals(u.email, 'joe2@somewhere.org') + self.assertTrue(u.check_password('')) + + new_io = StringIO() + call_command("createsuperuser", + interactive=False, + username="joe+admin@somewhere.org", + email="joe@somewhere.org", + stdout=new_io + ) + u = User.objects.get(username="joe+admin@somewhere.org") + self.assertEquals(u.email, 'joe@somewhere.org') + self.assertTrue(u.check_password('')) ->>> u = User.objects.get(username="joe+admin@somewhere.org") ->>> u.email -u'joe@somewhere.org' ->>> u.password -u'!' -"""