Auto-calculate password type length or allow it to be specified.

This commit is contained in:
Ryan Leckey
2013-08-14 15:12:34 -07:00
parent 4f987be1c6
commit 7ea50e436f
2 changed files with 43 additions and 4 deletions

View File

@@ -48,14 +48,15 @@ class PasswordType(types.TypeDecorator, ScalarCoercible):
"""
impl = types.BINARY(60)
impl = types.VARBINARY(1024)
python_type = Password
def __init__(self, **kwargs):
def __init__(self, max_length=None, **kwargs):
"""
All keyword arguments are forwarded to the construction of a
`passlib.context.CryptContext` object.
All keyword arguments (aside from max_length) are
forwarded to the construction of a `passlib.context.CryptContext`
object.
The following usage will create a password column that will
automatically hash new passwords as `pbkdf2_sha512` but still compare
@@ -84,6 +85,25 @@ class PasswordType(types.TypeDecorator, ScalarCoercible):
# Construct the passlib crypt context.
self.context = CryptContext(**kwargs)
if max_length is None:
# Calculate the largest possible encoded password.
# name + rounds + salt + hash + ($ * 4) of largest hash
max_lengths = []
for name in self.context.schemes():
scheme = getattr(__import__('passlib.hash').hash, name)
length = 4 + len(scheme.name)
length += len(str(getattr(scheme, 'max_rounds', '')))
length += scheme.max_salt_size or 0
length += getattr(scheme, 'encoded_checksum_size',
scheme.checksum_size)
max_lengths.append(length)
# Set the max_length to the maximum calculated max length.
max_length = max(max_lengths)
# Set the impl to the now-calculated max length.
self.impl = types.VARBINARY(max_length)
def process_bind_param(self, value, dialect):
if isinstance(value, Password):
# Value has already been hashed.

View File

@@ -1,6 +1,7 @@
from pytest import mark
import sqlalchemy as sa
from tests import TestCase
from sqlalchemy import inspect
from sqlalchemy_utils.types import password
from sqlalchemy_utils import Password, PasswordType
@@ -15,6 +16,7 @@ class TestPasswordType(TestCase):
password = sa.Column(PasswordType(
schemes=[
'pbkdf2_sha512',
'pbkdf2_sha256',
'md5_crypt'
],
@@ -67,3 +69,20 @@ class TestPasswordType(TestCase):
assert obj.password.hash.startswith('$1$')
assert obj.password == 'b'
assert obj.password.hash.startswith('$pbkdf2-sha512$')
def test_auto_column_length(self):
"""Should derive the correct column length from the specified schemes.
"""
from passlib.hash import pbkdf2_sha512
impl = inspect(self.User).c.password.type.impl
# name + rounds + salt + hash + ($ * 4) of largest hash
expected_length = len(pbkdf2_sha512.name)
expected_length += len(str(pbkdf2_sha512.max_rounds))
expected_length += pbkdf2_sha512.max_salt_size
expected_length += pbkdf2_sha512.encoded_checksum_size
expected_length += 4
assert impl.length == expected_length