Skip to content

Commit

Permalink
Merge pull request pallets-eco#389 from nickretallack/develop
Browse files Browse the repository at this point in the history
Reset Password Fixes
  • Loading branch information
Matt Wright committed May 26, 2015
2 parents 8a14aba + c10c905 commit 33252ae
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 5 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ env/
Session.vim
.netrwhist
*~

.eggs/README.txt
16 changes: 13 additions & 3 deletions flask_security/recoverable.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from flask import current_app as app
from werkzeug.local import LocalProxy
from werkzeug.security import safe_str_cmp

from .signals import password_reset, reset_password_instructions_sent
from .utils import send_mail, md5, encrypt_password, url_for_security, \
Expand Down Expand Up @@ -53,19 +54,28 @@ def generate_reset_password_token(user):
:param user: The user to work with
"""
data = [str(user.id), md5(user.password)]
password_hash = md5(user.password) if user.password else None
data = [str(user.id), password_hash]
return _security.reset_serializer.dumps(data)


def reset_password_token_status(token):
"""Returns the expired status, invalid status, and user of a password reset
token. For example::
expired, invalid, user = reset_password_token_status('...')
expired, invalid, user, data = reset_password_token_status('...')
:param token: The password reset token
"""
return get_token_status(token, 'reset', 'RESET_PASSWORD')
expired, invalid, user, data = get_token_status(token, 'reset', 'RESET_PASSWORD',
return_data=True)
if not invalid:
if user.password:
password_hash = md5(user.password)
if not safe_str_cmp(password_hash, data[1]):
invalid = True

return expired, invalid, user


def update_password(user, password):
Expand Down
8 changes: 6 additions & 2 deletions flask_security/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ def send_mail(subject, recipient, template, **context):
mail.send(msg)


def get_token_status(token, serializer, max_age=None):
def get_token_status(token, serializer, max_age=None, return_data=False):
"""Get the status of a token.
:param token: The token to check
Expand All @@ -367,7 +367,11 @@ def get_token_status(token, serializer, max_age=None):
user = _datastore.find_user(id=data[0])

expired = expired and (user is not None)
return expired, invalid, user

if return_data:
return expired, invalid, user, data
else:
return expired, invalid, user


def get_identity_attributes(app=None):
Expand Down
41 changes: 41 additions & 0 deletions tests/test_recoverable.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,47 @@ def test_expired_reset_token(client, get_message):
assert msg in response.data


def test_used_reset_token(client, get_message):
with capture_reset_password_requests() as requests:
client.post('/reset', data=dict(email='[email protected]'), follow_redirects=True)

token = requests[0]['token']

# use the token
response = client.post('/reset/' + token, data={
'password': 'newpassword',
'password_confirm': 'newpassword'
}, follow_redirects=True)

assert get_message('PASSWORD_RESET') in response.data

logout(client)

# attempt to use it a second time
response2 = client.post('/reset/' + token, data={
'password': 'otherpassword',
'password_confirm': 'otherpassword'
}, follow_redirects=True)

msg = get_message('INVALID_RESET_PASSWORD_TOKEN')
assert msg in response2.data


def test_reset_passwordless_user(client, get_message):
with capture_reset_password_requests() as requests:
client.post('/reset', data=dict(email='[email protected]'), follow_redirects=True)

token = requests[0]['token']

# use the token
response = client.post('/reset/' + token, data={
'password': 'newpassword',
'password_confirm': 'newpassword'
}, follow_redirects=True)

assert get_message('PASSWORD_RESET') in response.data


@pytest.mark.settings(reset_url='/custom_reset')
def test_custom_reset_url(client):
response = client.get('/custom_reset')
Expand Down

0 comments on commit 33252ae

Please sign in to comment.