Forces validation on Account.set_password() and provides an Account.validate_password() method to validate passwords.
This commit is contained in:
parent
ddf01d1631
commit
c8c9e831ee
4 changed files with 98 additions and 3 deletions
|
|
@ -13,6 +13,8 @@ instead for most things).
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import password_validation
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from evennia.typeclasses.models import TypeclassBase
|
from evennia.typeclasses.models import TypeclassBase
|
||||||
from evennia.accounts.manager import AccountManager
|
from evennia.accounts.manager import AccountManager
|
||||||
|
|
@ -357,7 +359,66 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
||||||
puppet = property(__get_single_puppet)
|
puppet = property(__get_single_puppet)
|
||||||
|
|
||||||
# utility methods
|
# utility methods
|
||||||
|
@classmethod
|
||||||
|
def validate_password(cls, password, account=None):
|
||||||
|
"""
|
||||||
|
Checks the given password against the list of Django validators enabled
|
||||||
|
in the server.conf file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
password (str): Password to validate
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
account (DefaultAccount, optional): Account object to validate the
|
||||||
|
password for. Optional, but Django includes some validators to
|
||||||
|
do things like making sure users aren't setting passwords to the
|
||||||
|
same value as their username. If left blank, these user-specific
|
||||||
|
checks are skipped.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
valid (bool): Whether or not the password passed validation
|
||||||
|
error (ValidationError, None): Any validation error(s) raised. Multiple
|
||||||
|
errors can be nested within a single object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
valid = False
|
||||||
|
error = None
|
||||||
|
|
||||||
|
# Validation returns None on success; invert it and return a more sensible bool
|
||||||
|
try:
|
||||||
|
valid = not password_validation.validate_password(password, user=account)
|
||||||
|
except ValidationError as e:
|
||||||
|
error = e
|
||||||
|
|
||||||
|
return valid, error
|
||||||
|
|
||||||
|
def set_password(self, password, force=False):
|
||||||
|
"""
|
||||||
|
Applies the given password to the account if it passes validation checks.
|
||||||
|
Can be overridden by using the 'force' flag.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
password (str): Password to set
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
force (bool): Sets password without running validation checks.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValidationError
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None (None): Does not return a value.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not force:
|
||||||
|
# Run validation checks
|
||||||
|
valid, error = self.validate_password(password, account=self)
|
||||||
|
if error: raise error
|
||||||
|
|
||||||
|
super(DefaultAccount, self).set_password(password)
|
||||||
|
logger.log_info("Password succesfully changed for %s." % self)
|
||||||
|
self.at_password_change()
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Deletes the account permanently.
|
Deletes the account permanently.
|
||||||
|
|
@ -714,6 +775,17 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def at_password_change(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Called after a successful password set/modify.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def at_pre_login(self, **kwargs):
|
def at_pre_login(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,29 @@ class TestDefaultAccount(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.s1 = Session()
|
self.s1 = Session()
|
||||||
self.s1.sessid = 0
|
self.s1.sessid = 0
|
||||||
|
|
||||||
|
def test_password_validation(self):
|
||||||
|
"Check password validators deny bad passwords"
|
||||||
|
|
||||||
|
self.account = create.create_account("TestAccount%s" % randint(0, 9), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
|
||||||
|
for bad in ('', '123', 'password', 'TestAccount', '#', 'xyzzy'):
|
||||||
|
self.assertFalse(self.account.validate_password(bad, account=self.account)[0])
|
||||||
|
|
||||||
|
"Check validators allow sufficiently complex passwords"
|
||||||
|
for better in ('Mxyzptlk', "j0hn, i'M 0n1y d4nc1nG"):
|
||||||
|
self.assertTrue(self.account.validate_password(better, account=self.account)[0])
|
||||||
|
|
||||||
|
def test_password_change(self):
|
||||||
|
"Check password setting and validation is working as expected"
|
||||||
|
self.account = create.create_account("TestAccount%s" % randint(0, 9), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
# Try setting some bad passwords
|
||||||
|
for bad in ('', '#', 'TestAccount', 'password'):
|
||||||
|
self.assertRaises(ValidationError, self.account.set_password, bad)
|
||||||
|
|
||||||
|
# Try setting a better password (test for False; returns None on success)
|
||||||
|
self.assertFalse(self.account.set_password('Mxyzptlk'))
|
||||||
|
|
||||||
def test_puppet_object_no_object(self):
|
def test_puppet_object_no_object(self):
|
||||||
"Check puppet_object method called with no object param"
|
"Check puppet_object method called with no object param"
|
||||||
|
|
@ -157,4 +180,4 @@ class TestDefaultAccount(TestCase):
|
||||||
|
|
||||||
account.puppet_object(self.s1, obj)
|
account.puppet_object(self.s1, obj)
|
||||||
self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("is already puppeted by another Account."))
|
self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("is already puppeted by another Account."))
|
||||||
self.assertIsNone(obj.at_post_puppet.call_args)
|
self.assertIsNone(obj.at_post_puppet.call_args)
|
||||||
|
|
@ -821,7 +821,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'NAME': 'evennia.contrib.security.validators.EvenniaPasswordValidator',
|
'NAME': 'evennia.server.validators.EvenniaPasswordValidator',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue