0002-views-use-one-time-token-for-password-reset-41792.patch
src/authentic2/compat/misc.py | ||
---|---|---|
18 | 18 |
import inspect |
19 | 19 | |
20 | 20 |
from django.conf import settings |
21 |
from django.contrib.auth.tokens import PasswordResetTokenGenerator |
|
22 | 21 |
from django.utils import six |
23 | 22 | |
24 | 23 |
try: |
... | ... | |
35 | 34 | |
36 | 35 |
user_model_label = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') |
37 | 36 | |
38 |
default_token_generator = PasswordResetTokenGenerator() |
|
39 | ||
40 | 37 |
if six.PY2: |
41 | 38 |
Base64Error = TypeError |
42 | 39 |
else: |
src/authentic2/urls.py | ||
---|---|---|
75 | 75 |
name='password_change_done'), |
76 | 76 | |
77 | 77 |
# Password reset |
78 |
url(r'^password/reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
|
|
78 |
url(r'^password/reset/confirm/(?P<token>[A-Za-z0-9_ -]+)/$',
|
|
79 | 79 |
views.password_reset_confirm, |
80 | 80 |
name='password_reset_confirm'), |
81 | 81 |
url(r'^password/reset/$', |
src/authentic2/utils/__init__.py | ||
---|---|---|
794 | 794 | |
795 | 795 |
def build_reset_password_url(user, request=None, next_url=None, set_random_password=True, sign_next_url=True): |
796 | 796 |
'''Build a reset password URL''' |
797 |
from authentic2.compat.misc import default_token_generator
|
|
797 |
from authentic2.models import Token
|
|
798 | 798 | |
799 | 799 |
if set_random_password: |
800 | 800 |
user.set_password(uuid.uuid4().hex) |
801 | 801 |
user.save() |
802 |
uid = urlsafe_base64_encode(force_bytes(user.pk)) |
|
803 |
token = default_token_generator.make_token(user) |
|
802 |
lifetime = settings.PASSWORD_RESET_TIMEOUT_DAYS * 3600 * 24 |
|
803 |
# invalidate any token associated with this user |
|
804 |
Token.objects.filter(kind='pw-reset', content__user=user.pk).delete() |
|
805 |
token = Token.create('pw-reset', {'user': user.pk}, duration=lifetime) |
|
804 | 806 |
reset_url = make_url( |
805 | 807 |
'password_reset_confirm', |
806 |
kwargs={'uidb64': uid, 'token': token},
|
|
808 |
kwargs={'token': token.uuid_b64url},
|
|
807 | 809 |
next_url=next_url, |
808 | 810 |
sign_next_url=sign_next_url, |
809 | 811 |
request=request, |
... | ... | |
848 | 850 |
legacy_body_templates=legacy_body_templates, |
849 | 851 |
per_ou_templates=True, **kwargs) |
850 | 852 |
logger.info(u'password reset request for user %s, email sent to %s ' |
851 |
'with token %s', user, user.email, token[:9])
|
|
853 |
'with token %s', user, user.email, token.uuid)
|
|
852 | 854 | |
853 | 855 | |
854 | 856 |
def batch(iterable, size): |
src/authentic2/views.py | ||
---|---|---|
54 | 54 |
from django.http import HttpResponseBadRequest |
55 | 55 |
from django.template import loader |
56 | 56 | |
57 |
from authentic2.compat.misc import default_token_generator |
|
58 | 57 |
from . import (utils, app_settings, decorators, constants, |
59 | 58 |
models, cbv, hooks, validators) |
60 | 59 |
from .utils import switch_user |
... | ... | |
710 | 709 |
] |
711 | 710 | |
712 | 711 |
def dispatch(self, request, *args, **kwargs): |
713 |
validlink = True |
|
714 |
uidb64 = kwargs['uidb64'] |
|
715 |
self.token = token = kwargs['token'] |
|
712 |
token = kwargs['token'].replace(' ', '') |
|
713 |
try: |
|
714 |
self.token = models.Token.use('pw-reset', token, delete=False) |
|
715 |
except models.Token.DoesNotExist: |
|
716 |
messages.warning(request, _('Password reset token is unknown or expired')) |
|
717 |
return utils.redirect(request, self.get_success_url()) |
|
718 |
except (TypeError, ValueError): |
|
719 |
messages.warning(request, _('Password reset token is invalid')) |
|
720 |
return utils.redirect(request, self.get_success_url()) |
|
716 | 721 | |
717 |
UserModel = get_user_model() |
|
718 |
# checked by URLconf |
|
719 |
assert uidb64 is not None and token is not None |
|
722 |
uid = self.token.content['user'] |
|
720 | 723 |
try: |
721 |
uid = urlsafe_base64_decode(uidb64) |
|
722 | 724 |
# use authenticate to eventually get an LDAPUser |
723 |
self.user = utils.authenticate(request, user=UserModel._default_manager.get(pk=uid)) |
|
724 |
except (TypeError, ValueError, OverflowError, |
|
725 |
UserModel.DoesNotExist): |
|
726 |
validlink = False |
|
725 |
self.user = utils.authenticate(request, user=User._default_manager.get(pk=uid)) |
|
726 |
except (TypeError, ValueError, OverflowError, User.DoesNotExist): |
|
727 | 727 |
messages.warning(request, _('User not found')) |
728 | ||
729 |
if validlink and not default_token_generator.check_token(self.user, token): |
|
730 |
validlink = False |
|
731 |
messages.warning(request, _('You reset password link is invalid or has expired')) |
|
732 |
if not validlink: |
|
733 | 728 |
return utils.redirect(request, self.get_success_url()) |
729 | ||
734 | 730 |
can_reset_password = utils.get_user_flag(user=self.user, |
735 | 731 |
name='can_reset_password', |
736 | 732 |
default=self.user.has_usable_password()) |
... | ... | |
739 | 735 |
request, |
740 | 736 |
_('It\'s not possible to reset your password. Please contact an administrator.')) |
741 | 737 |
return utils.redirect(request, self.get_success_url()) |
742 |
return super(PasswordResetConfirmView, self).dispatch(request, *args, |
|
743 |
**kwargs) |
|
738 |
return super(PasswordResetConfirmView, self).dispatch(request, *args, **kwargs) |
|
744 | 739 | |
745 | 740 |
def get_context_data(self, **kwargs): |
746 | 741 |
ctx = super(PasswordResetConfirmView, self).get_context_data(**kwargs) |
... | ... | |
760 | 755 |
form.save() |
761 | 756 |
hooks.call_hooks('event', name='password-reset-confirm', user=form.user, token=self.token, |
762 | 757 |
form=form) |
763 |
logger.info(u'password reset for user %s with token %r', |
|
764 |
self.user, self.token[:9])
|
|
758 |
logger.info(u'password reset for user %s with token %r', self.user, self.token.uuid)
|
|
759 |
self.token.delete()
|
|
765 | 760 |
return self.finish() |
766 | 761 | |
767 | 762 |
def finish(self): |
768 |
- |