views: use one-time token for password reset (#41792)

This commit is contained in:
Valentin Deniaud 2020-04-22 18:07:03 +02:00 committed by Benjamin Dauvergne
parent f7e5ad16da
commit 7d9e65dc96
4 changed files with 27 additions and 33 deletions

View File

@ -18,7 +18,6 @@ from datetime import datetime
import inspect
from django.conf import settings
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six
try:
@ -35,8 +34,6 @@ except ImportError:
user_model_label = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
default_token_generator = PasswordResetTokenGenerator()
if six.PY2:
Base64Error = TypeError
else:

View File

@ -85,7 +85,7 @@ accounts_urlpatterns = [
name='password_change_done'),
# Password reset
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})/$',
url(r'^password/reset/confirm/(?P<token>[A-Za-z0-9_ -]+)/$',
views.password_reset_confirm,
name='password_reset_confirm'),
url(r'^password/reset/$',

View File

@ -797,16 +797,18 @@ def send_account_deletion_mail(request, user):
def build_reset_password_url(user, request=None, next_url=None, set_random_password=True, sign_next_url=True):
'''Build a reset password URL'''
from authentic2.compat.misc import default_token_generator
from authentic2.models import Token
if set_random_password:
user.set_password(uuid.uuid4().hex)
user.save()
uid = urlsafe_base64_encode(force_bytes(user.pk))
token = default_token_generator.make_token(user)
lifetime = settings.PASSWORD_RESET_TIMEOUT_DAYS * 3600 * 24
# invalidate any token associated with this user
Token.objects.filter(kind='pw-reset', content__user=user.pk).delete()
token = Token.create('pw-reset', {'user': user.pk}, duration=lifetime)
reset_url = make_url(
'password_reset_confirm',
kwargs={'uidb64': uid, 'token': token},
kwargs={'token': token.uuid_b64url},
next_url=next_url,
sign_next_url=sign_next_url,
request=request,
@ -851,7 +853,7 @@ def send_password_reset_mail(user, template_names=None, request=None,
legacy_body_templates=legacy_body_templates,
per_ou_templates=True, **kwargs)
logger.info(u'password reset request for user %s, email sent to %s '
'with token %s', user, user.email, token[:9])
'with token %s', user, user.email, token.uuid)
def batch(iterable, size):

View File

@ -52,7 +52,6 @@ from django.http import HttpResponseBadRequest
from django.template import loader
from authentic2.custom_user.models import iter_attributes
from authentic2.compat.misc import default_token_generator
from . import (utils, app_settings, decorators, constants,
models, cbv, hooks, validators, attribute_kinds)
from .utils import switch_user
@ -710,27 +709,24 @@ class PasswordResetConfirmView(cbv.RedirectToNextURLViewMixin, FormView):
]
def dispatch(self, request, *args, **kwargs):
validlink = True
uidb64 = kwargs['uidb64']
self.token = token = kwargs['token']
UserModel = get_user_model()
# checked by URLconf
assert uidb64 is not None and token is not None
token = kwargs['token'].replace(' ', '')
try:
uid = urlsafe_base64_decode(uidb64)
# use authenticate to eventually get an LDAPUser
self.user = utils.authenticate(request, user=UserModel._default_manager.get(pk=uid))
except (TypeError, ValueError, OverflowError,
UserModel.DoesNotExist):
validlink = False
messages.warning(request, _('User not found'))
if validlink and not default_token_generator.check_token(self.user, token):
validlink = False
messages.warning(request, _('You reset password link is invalid or has expired'))
if not validlink:
self.token = models.Token.use('pw-reset', token, delete=False)
except models.Token.DoesNotExist:
messages.warning(request, _('Password reset token is unknown or expired'))
return utils.redirect(request, self.get_success_url())
except (TypeError, ValueError):
messages.warning(request, _('Password reset token is invalid'))
return utils.redirect(request, self.get_success_url())
uid = self.token.content['user']
try:
# use authenticate to eventually get an LDAPUser
self.user = utils.authenticate(request, user=User._default_manager.get(pk=uid))
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
messages.warning(request, _('User not found'))
return utils.redirect(request, self.get_success_url())
can_reset_password = utils.get_user_flag(user=self.user,
name='can_reset_password',
default=self.user.has_usable_password())
@ -739,8 +735,7 @@ class PasswordResetConfirmView(cbv.RedirectToNextURLViewMixin, FormView):
request,
_('It\'s not possible to reset your password. Please contact an administrator.'))
return utils.redirect(request, self.get_success_url())
return super(PasswordResetConfirmView, self).dispatch(request, *args,
**kwargs)
return super(PasswordResetConfirmView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
ctx = super(PasswordResetConfirmView, self).get_context_data(**kwargs)
@ -760,8 +755,8 @@ class PasswordResetConfirmView(cbv.RedirectToNextURLViewMixin, FormView):
form.save()
hooks.call_hooks('event', name='password-reset-confirm', user=form.user, token=self.token,
form=form)
logger.info(u'password reset for user %s with token %r',
self.user, self.token[:9])
logger.info(u'password reset for user %s with token %r', self.user, self.token.uuid)
self.token.delete()
return self.finish()
def finish(self):