Projet

Général

Profil

0002-views-use-one-time-token-for-password-reset-41792.patch

Valentin Deniaud, 29 avril 2020 14:14

Télécharger (6,96 ko)

Voir les différences:

Subject: [PATCH 2/4] views: use one-time token for password reset (#41792)

 src/authentic2/compat/misc.py    |  3 ---
 src/authentic2/urls.py           |  2 +-
 src/authentic2/utils/__init__.py | 12 ++++++-----
 src/authentic2/views.py          | 37 ++++++++++++++------------------
 4 files changed, 24 insertions(+), 30 deletions(-)
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
-