diff --git a/flask_security/core.py b/flask_security/core.py index c29de69..6b58b3d 100644 --- a/flask_security/core.py +++ b/flask_security/core.py @@ -94,6 +94,16 @@ _default_messages = { 'REFRESH': ('Please reauthenticate to access this page.', 'info') } +_allowed_password_hash_schemes = [ + 'bcrypt', + 'des_crypt', + 'pbkdf2_sha256', + 'pbkdf2_sha512', + 'sha256_crypt', + 'sha512_crypt', + # And always last one... + 'plaintext' +] def _user_loader(user_id): return _security.datastore.find_user(id=user_id) @@ -149,7 +159,10 @@ def _get_principal(app): def _get_pwd_context(app): pw_hash = cv('PASSWORD_HASH', app=app) - return CryptContext(schemes=[pw_hash], default=pw_hash) + if pw_hash not in _allowed_password_hash_schemes: + allowed = ', '.join(_allowed_password_hash_schemes[:-1]) + ' and ' + _allowed_password_hash_schemes[-1] + raise ValueError("Invalid hash scheme %r. Allowed values are %s" % (pw_hash, allowed)) + return CryptContext(schemes=_allowed_password_hash_schemes, default=pw_hash) def _get_serializer(app, name): diff --git a/flask_security/decorators.py b/flask_security/decorators.py index 32e3409..5787679 100644 --- a/flask_security/decorators.py +++ b/flask_security/decorators.py @@ -67,7 +67,8 @@ def _check_http_auth(): auth = request.authorization or dict(username=None, password=None) user = _security.datastore.find_user(email=auth.username) - if user and utils.verify_password(auth.password, user.password): + if user and utils.verify_and_update_password(auth.password, user): + _security.datastore.commit() app = current_app._get_current_object() identity_changed.send(app, identity=Identity(user.id)) return True diff --git a/flask_security/forms.py b/flask_security/forms.py index dc99118..3539f63 100644 --- a/flask_security/forms.py +++ b/flask_security/forms.py @@ -16,7 +16,7 @@ from flask.ext.wtf import Form as BaseForm, TextField, PasswordField, \ from werkzeug.local import LocalProxy from .confirmable import requires_confirmation -from .utils import verify_password, get_message +from .utils import verify_and_update_password, get_message # Convenient reference _datastore = LocalProxy(lambda: current_app.extensions['security'].datastore) @@ -162,7 +162,7 @@ class LoginForm(Form, NextFormMixin): if self.user is None: self.email.errors.append('Specified user does not exist') return False - if not verify_password(self.password.data, self.user.password): + if not verify_and_update_password(self.password.data, self.user): self.password.errors.append('Invalid password') return False if requires_confirmation(self.user): diff --git a/flask_security/utils.py b/flask_security/utils.py index 03becf6..5531ec0 100644 --- a/flask_security/utils.py +++ b/flask_security/utils.py @@ -86,6 +86,14 @@ def verify_password(password, password_hash): return _pwd_context.verify(get_hmac(password), password_hash) +def verify_and_update_password(password, user): + verified, new_password = _pwd_context.verify_and_update(get_hmac(password), user.password) + if verified and new_password: + user.password = new_password + _datastore.put(user) + return verified + + def encrypt_password(password): return _pwd_context.encrypt(get_hmac(password))