mirror of
https://github.com/evennia/evennia.git
synced 2026-03-24 16:56:32 +01:00
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
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import password_validation
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import timezone
|
||||
from evennia.typeclasses.models import TypeclassBase
|
||||
from evennia.accounts.manager import AccountManager
|
||||
|
|
@ -357,7 +359,66 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
|||
puppet = property(__get_single_puppet)
|
||||
|
||||
# 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):
|
||||
"""
|
||||
Deletes the account permanently.
|
||||
|
|
@ -714,6 +775,17 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
|||
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -57,6 +57,29 @@ class TestDefaultAccount(TestCase):
|
|||
def setUp(self):
|
||||
self.s1 = Session()
|
||||
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):
|
||||
"Check puppet_object method called with no object param"
|
||||
|
|
@ -157,4 +180,4 @@ class TestDefaultAccount(TestCase):
|
|||
|
||||
account.puppet_object(self.s1, obj)
|
||||
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': 'evennia.contrib.security.validators.EvenniaPasswordValidator',
|
||||
'NAME': 'evennia.server.validators.EvenniaPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue