Source code for pyramid_fullauth.models.mixins.password
# Copyright (c) 2013 - 2016 by pyramid_fullauth authors and contributors <see AUTHORS file>
#
# This module is part of pyramid_fullauth and is released under
# the MIT License (MIT): http://opensource.org/licenses/MIT
"""Password mixin related module."""
import hashlib
import os
import uuid
from hashlib import algorithms_guaranteed
from sqlalchemy import Column, Enum, String, Unicode
from sqlalchemy.orm import validates
from pyramid_fullauth.exceptions import EmptyError
[docs]
class UserPasswordMixin(object):
"""Authentication field definition along with appropriate methods."""
#: password field
password = Column(Unicode(128), nullable=False)
#: hash_algorithm field
_hash_algorithm = Column(
"hash_algorithm",
Enum(*algorithms_guaranteed, name="hash_algorithms_enum"),
default="sha256",
nullable=False,
)
#: salt field
_salt = Column("salt", Unicode(128), nullable=False)
#: reset key field
reset_key = Column(String(255), unique=True)
[docs]
def check_password(self, password):
"""Check if password correspond to the saved one.
:param str password: password to compare
:returns: True, if password is same, False if not
:rtype: bool
"""
if password and self.hash_password(password, self._salt, self._hash_algorithm) == self.password:
return True
return False
def set_reset(self):
"""Generate password reset key."""
self.reset_key = str(uuid.uuid4())
[docs]
@classmethod
def hash_password(cls, password, salt, hash_method):
"""Produce hash out of a password.
:param str password: password string, not hashed
:param str salt: salt
:param callable hash_method: a hash method which will be used to generate hash
:returns: hashed password
:rtype: str
"""
# let's allow passing just method name
if not callable(hash_method):
hash_method = getattr(hashlib, hash_method)
# let's convert password to bytest from string
if isinstance(password, str):
password = password.encode("utf-8")
# to bytest from string
if isinstance(salt, str):
salt = salt.encode("utf-8")
# generating salted hash
return hash_method(password + salt).hexdigest()
[docs]
@validates("password")
def password_validator(self, _, password):
"""Validate password.
Password validator keeps new password hashed.
Rises Value error on empty password
:param str key: field key
:param str password: new password
:returns: hashed and salted password
:rtype: str
:raises: pyramid_fullauth.exceptions.EmptyError
.. note::
If you're using this Mixin on your own User object,
don't forget to add a listener as well, like that:
.. code-block:: python
from sqlalchemy.event import listen
listen(User.password, 'set', User.password_listener, retval=True)
.. note::
For more information on Attribute Events in sqlalchemy see:
:meth:`sqlalchemy.orm.events.AttributeEvents.set`
"""
if not password:
raise EmptyError("password-empty")
# reading default hash_algorithm
# pylint:disable=protected-access
hash_algorithm = self.__class__._hash_algorithm.property.columns[0].default.arg
# pylint:enable=protected-access
# getting currently used hash method
hash_method = getattr(hashlib, hash_algorithm)
# generating salt
salt = hash_method()
salt.update(os.urandom(60))
salt_value = salt.hexdigest()
# storing used hash algorithm
self._hash_algorithm = hash_algorithm
self._salt = salt_value
return self.__class__.hash_password(password, salt_value, hash_method)