Kaydet (Commit) 8755fb15 authored tarafından Russell Keith-Magee's avatar Russell Keith-Magee

Fixed #14354 -- Normalized the handling of empty/null passwords in contrib.auth.…

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
üst 71a4c472
...@@ -310,6 +310,7 @@ answer newbie questions, and generally made Django that much better: ...@@ -310,6 +310,7 @@ answer newbie questions, and generally made Django that much better:
Simon Litchfield <simon@quo.com.au> Simon Litchfield <simon@quo.com.au>
Daniel Lindsley <polarcowz@gmail.com> Daniel Lindsley <polarcowz@gmail.com>
Trey Long <trey@ktrl.com> Trey Long <trey@ktrl.com>
Laurent Luce <http://www.laurentluce.com>
Martin Mahner <http://www.mahner.org/> Martin Mahner <http://www.mahner.org/>
Matt McClanahan <http://mmcc.cx/> Matt McClanahan <http://mmcc.cx/>
Stanislaus Madueke Stanislaus Madueke
......
...@@ -41,7 +41,8 @@ class Command(BaseCommand): ...@@ -41,7 +41,8 @@ class Command(BaseCommand):
username = options.get('username', None) username = options.get('username', None)
email = options.get('email', None) email = options.get('email', None)
interactive = options.get('interactive') interactive = options.get('interactive')
verbosity = int(options.get('verbosity', 1))
# Do quick and dirty validation if --noinput # Do quick and dirty validation if --noinput
if not interactive: if not interactive:
if not username or not email: if not username or not email:
...@@ -79,7 +80,7 @@ class Command(BaseCommand): ...@@ -79,7 +80,7 @@ class Command(BaseCommand):
# try/except to trap for a keyboard interrupt and exit gracefully. # try/except to trap for a keyboard interrupt and exit gracefully.
if interactive: if interactive:
try: try:
# Get a username # Get a username
while 1: while 1:
if not username: if not username:
...@@ -100,7 +101,7 @@ class Command(BaseCommand): ...@@ -100,7 +101,7 @@ class Command(BaseCommand):
else: else:
sys.stderr.write("Error: That username is already taken.\n") sys.stderr.write("Error: That username is already taken.\n")
username = None username = None
# Get an email # Get an email
while 1: while 1:
if not email: if not email:
...@@ -112,7 +113,7 @@ class Command(BaseCommand): ...@@ -112,7 +113,7 @@ class Command(BaseCommand):
email = None email = None
else: else:
break break
# Get a password # Get a password
while 1: while 1:
if not password: if not password:
...@@ -130,6 +131,8 @@ class Command(BaseCommand): ...@@ -130,6 +131,8 @@ class Command(BaseCommand):
except KeyboardInterrupt: except KeyboardInterrupt:
sys.stderr.write("\nOperation cancelled.\n") sys.stderr.write("\nOperation cancelled.\n")
sys.exit(1) sys.exit(1)
User.objects.create_superuser(username, email, password) User.objects.create_superuser(username, email, password)
print "Superuser created successfully." if verbosity >= 1:
self.stdout.write("Superuser created successfully.\n")
...@@ -106,7 +106,6 @@ class UserManager(models.Manager): ...@@ -106,7 +106,6 @@ class UserManager(models.Manager):
""" """
Creates and saves a User with the given username, e-mail and password. Creates and saves a User with the given username, e-mail and password.
""" """
now = datetime.datetime.now() now = datetime.datetime.now()
# Normalize the address by lowercasing the domain part of the email # Normalize the address by lowercasing the domain part of the email
...@@ -122,10 +121,7 @@ class UserManager(models.Manager): ...@@ -122,10 +121,7 @@ class UserManager(models.Manager):
is_active=True, is_superuser=False, last_login=now, is_active=True, is_superuser=False, last_login=now,
date_joined=now) date_joined=now)
if password: user.set_password(password)
user.set_password(password)
else:
user.set_unusable_password()
user.save(using=self._db) user.save(using=self._db)
return user return user
...@@ -238,11 +234,14 @@ class User(models.Model): ...@@ -238,11 +234,14 @@ class User(models.Model):
return full_name.strip() return full_name.strip()
def set_password(self, raw_password): def set_password(self, raw_password):
import random if raw_password is None:
algo = 'sha1' self.set_unusable_password()
salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5] else:
hsh = get_hexdigest(algo, salt, raw_password) import random
self.password = '%s$%s$%s' % (algo, salt, hsh) 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): def check_password(self, raw_password):
""" """
...@@ -265,7 +264,11 @@ class User(models.Model): ...@@ -265,7 +264,11 @@ class User(models.Model):
self.password = UNUSABLE_PASSWORD self.password = UNUSABLE_PASSWORD
def has_usable_password(self): 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): def get_group_permissions(self, obj=None):
""" """
......
from django.contrib.auth.tests.auth_backends import BackendTest, RowlevelBackendTest, AnonymousUserBackendTest, NoAnonymousUserBackendTest 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.decorators import LoginRequiredTestCase
from django.contrib.auth.tests.forms import UserCreationFormTest, AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest, UserChangeFormTest, PasswordResetFormTest from django.contrib.auth.tests.forms import UserCreationFormTest, AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest, UserChangeFormTest, PasswordResetFormTest
from django.contrib.auth.tests.remote_user \ from django.contrib.auth.tests.remote_user \
...@@ -12,6 +12,5 @@ from django.contrib.auth.tests.views \ ...@@ -12,6 +12,5 @@ from django.contrib.auth.tests.views \
# The password for the fixture data users is 'password' # The password for the fixture data users is 'password'
__test__ = { __test__ = {
'BASIC_TESTS': BASIC_TESTS,
'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS, 'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS,
} }
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 = """ class BasicTestCase(TestCase):
>>> from django.contrib.auth.models import User, AnonymousUser def test_user(self):
>>> u = User.objects.create_user('testuser', 'test@example.com', 'testpw') "Check that users can be created and can set their password"
>>> u.has_usable_password() u = User.objects.create_user('testuser', 'test@example.com', 'testpw')
True self.assertTrue(u.has_usable_password())
>>> u.check_password('bad') self.assertFalse(u.check_password('bad'))
False self.assertTrue(u.check_password('testpw'))
>>> 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
>>> u.is_authenticated() # Check we can manually set an unusable password
True u.set_unusable_password()
>>> u.is_staff u.save()
False self.assertFalse(u.check_password('testpw'))
>>> u.is_active self.assertFalse(u.has_usable_password())
True u.set_password('testpw')
>>> u.is_superuser self.assertTrue(u.check_password('testpw'))
False u.set_password(None)
self.assertFalse(u.has_usable_password())
>>> a = AnonymousUser() # Check authentication/permissions
>>> a.is_authenticated() self.assertTrue(u.is_authenticated())
False self.assertFalse(u.is_staff)
>>> a.is_staff self.assertTrue(u.is_active)
False self.assertFalse(u.is_superuser)
>>> a.is_active
False
>>> a.is_superuser
False
>>> a.groups.all()
[]
>>> a.user_permissions.all()
[]
# superuser tests. # Check API-based user creation with no password
>>> super = User.objects.create_superuser('super', 'super@example.com', 'super') u2 = User.objects.create_user('testuser2', 'test2@example.com')
>>> super.is_superuser self.assertFalse(u.has_usable_password())
True
>>> super.is_active
True
>>> super.is_staff
True
# def test_anonymous_user(self):
# Tests for createsuperuser management command. "Check the properties of the anonymous user"
# It's nearly impossible to test the interactive mode -- a command test helper a = AnonymousUser()
# would be needed (and *awesome*) -- so just test the non-interactive mode. self.assertFalse(a.is_authenticated())
# This covers most of the important validation, but not all. self.assertFalse(a.is_staff)
# self.assertFalse(a.is_active)
>>> from django.core.management import call_command 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") def test_superuser(self):
Superuser created successfully. "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") def test_createsuperuser_management_command(self):
>>> u.email "Check the operation of the createsuperuser management command"
u'joe@somewhere.org' # We can use the management command to create a superuser
>>> u.password new_io = StringIO()
u'!' call_command("createsuperuser",
>>> call_command("createsuperuser", interactive=False, username="joe+admin@somewhere.org", email="joe@somewhere.org") interactive=False,
Superuser created successfully. 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'!'
"""
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment