From bfae6ddb629837dca9e18c8db0f765832ad25907 Mon Sep 17 00:00:00 2001 From: sketchytechky Date: Wed, 15 Apr 2015 13:32:50 -0700 Subject: [PATCH 1/3] Generating link both from user.pk and user.password to mitigate issue with permanent link exposed to gain access --- password_reset/views.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/password_reset/views.py b/password_reset/views.py index 88b84a07..2ad71365 100644 --- a/password_reset/views.py +++ b/password_reset/views.py @@ -10,6 +10,7 @@ from django.template import loader from django.utils import timezone from django.views import generic +import hashlib from .forms import PasswordRecoveryForm, PasswordResetForm from .utils import get_user_model, get_username @@ -82,7 +83,13 @@ def send_notification(self): 'site': self.get_site(), 'user': self.user, 'username': get_username(self.user), - 'token': signing.dumps(self.user.pk, salt=self.salt), + 'token': signing.dumps({ + 'pk': self.user.pk, + 'psw': hashlib.sha256( + self.user.password + ) + }, + salt=self.salt), 'secure': self.request.is_secure(), } body = loader.render_to_string(self.email_template_name, @@ -121,12 +128,25 @@ def dispatch(self, request, *args, **kwargs): self.kwargs = kwargs try: - pk = signing.loads(kwargs['token'], max_age=self.token_expires, - salt=self.salt) + unsigned_pk_hash = signing.loads(kwargs['token'], + max_age=self.token_expires, + salt=self.salt) except signing.BadSignature: return self.invalid() + try: + pk = unsigned_pk_hash['pk'] + password = unsigned_pk_hash['psw'] + except KeyError: + return self.invalid() + self.user = get_object_or_404(get_user_model(), pk=pk) + + # Ensure the hashed password is same to prevent link to be reused + # TODO: this is assuming the password is changed + if password != hashlib.sha256(self.user.password): + return self.invalid() + return super(Reset, self).dispatch(request, *args, **kwargs) def invalid(self): From 46d6f2c4ebfbdda27954cfbffd4be14b92708fcf Mon Sep 17 00:00:00 2001 From: sketchytechky Date: Wed, 15 Apr 2015 16:29:03 -0700 Subject: [PATCH 2/3] Tidy up password hashing call --- password_reset/views.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/password_reset/views.py b/password_reset/views.py index 2ad71365..e9077642 100644 --- a/password_reset/views.py +++ b/password_reset/views.py @@ -21,6 +21,9 @@ class SaltMixin(object): salt = 'password_recovery' url_salt = 'password_recovery_url' + def hash_password(self, psw): + return hashlib.sha512(psw).hexdigest() + def loads_with_timestamp(value, salt): """Returns the unsigned value along with its timestamp, the time when it @@ -83,13 +86,14 @@ def send_notification(self): 'site': self.get_site(), 'user': self.user, 'username': get_username(self.user), - 'token': signing.dumps({ - 'pk': self.user.pk, - 'psw': hashlib.sha256( - self.user.password - ) - }, - salt=self.salt), + 'token': signing.dumps( + { + 'pk': self.user.pk, + 'psw': self.hash_password( + self.user.password + ) + }, + salt=self.salt), 'secure': self.request.is_secure(), } body = loader.render_to_string(self.email_template_name, @@ -144,7 +148,7 @@ def dispatch(self, request, *args, **kwargs): # Ensure the hashed password is same to prevent link to be reused # TODO: this is assuming the password is changed - if password != hashlib.sha256(self.user.password): + if password != self.hash_password(self.user.password): return self.invalid() return super(Reset, self).dispatch(request, *args, **kwargs) From b4e45c9047363bcd9391e71c19867e55462254b7 Mon Sep 17 00:00:00 2001 From: sketchytechky Date: Wed, 15 Apr 2015 16:50:32 -0700 Subject: [PATCH 3/3] Use django hasher to avoid handling hashlib different across py27 and py33 --- password_reset/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/password_reset/views.py b/password_reset/views.py index e9077642..db85d587 100644 --- a/password_reset/views.py +++ b/password_reset/views.py @@ -10,7 +10,7 @@ from django.template import loader from django.utils import timezone from django.views import generic -import hashlib +from django.contrib.auth.hashers import get_hasher from .forms import PasswordRecoveryForm, PasswordResetForm from .utils import get_user_model, get_username @@ -22,7 +22,7 @@ class SaltMixin(object): url_salt = 'password_recovery_url' def hash_password(self, psw): - return hashlib.sha512(psw).hexdigest() + return get_hasher().encode(psw, self.salt) def loads_with_timestamp(value, salt):