Auto-calculate password type length or allow it to be specified.
This commit is contained in:
		| @@ -48,14 +48,15 @@ class PasswordType(types.TypeDecorator, ScalarCoercible): | |||||||
|  |  | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     impl = types.BINARY(60) |     impl = types.VARBINARY(1024) | ||||||
|  |  | ||||||
|     python_type = Password |     python_type = Password | ||||||
|  |  | ||||||
|     def __init__(self, **kwargs): |     def __init__(self, max_length=None, **kwargs): | ||||||
|         """ |         """ | ||||||
|         All keyword arguments are forwarded to the construction of a |         All keyword arguments (aside from max_length) are | ||||||
|         `passlib.context.CryptContext` object. |         forwarded to the construction of a `passlib.context.CryptContext` | ||||||
|  |         object. | ||||||
|  |  | ||||||
|         The following usage will create a password column that will |         The following usage will create a password column that will | ||||||
|         automatically hash new passwords as `pbkdf2_sha512` but still compare |         automatically hash new passwords as `pbkdf2_sha512` but still compare | ||||||
| @@ -84,6 +85,25 @@ class PasswordType(types.TypeDecorator, ScalarCoercible): | |||||||
|         # Construct the passlib crypt context. |         # Construct the passlib crypt context. | ||||||
|         self.context = CryptContext(**kwargs) |         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): |     def process_bind_param(self, value, dialect): | ||||||
|         if isinstance(value, Password): |         if isinstance(value, Password): | ||||||
|             # Value has already been hashed. |             # Value has already been hashed. | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| from pytest import mark | from pytest import mark | ||||||
| import sqlalchemy as sa | import sqlalchemy as sa | ||||||
| from tests import TestCase | from tests import TestCase | ||||||
|  | from sqlalchemy import inspect | ||||||
| from sqlalchemy_utils.types import password | from sqlalchemy_utils.types import password | ||||||
| from sqlalchemy_utils import Password, PasswordType | from sqlalchemy_utils import Password, PasswordType | ||||||
|  |  | ||||||
| @@ -15,6 +16,7 @@ class TestPasswordType(TestCase): | |||||||
|             password = sa.Column(PasswordType( |             password = sa.Column(PasswordType( | ||||||
|                 schemes=[ |                 schemes=[ | ||||||
|                     'pbkdf2_sha512', |                     'pbkdf2_sha512', | ||||||
|  |                     'pbkdf2_sha256', | ||||||
|                     'md5_crypt' |                     'md5_crypt' | ||||||
|                 ], |                 ], | ||||||
|  |  | ||||||
| @@ -67,3 +69,20 @@ class TestPasswordType(TestCase): | |||||||
|         assert obj.password.hash.startswith('$1$') |         assert obj.password.hash.startswith('$1$') | ||||||
|         assert obj.password == 'b' |         assert obj.password == 'b' | ||||||
|         assert obj.password.hash.startswith('$pbkdf2-sha512$') |         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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Ryan Leckey
					Ryan Leckey