views: use one-time token for password reset (#41792)
This commit is contained in:
parent
f7e5ad16da
commit
7d9e65dc96
|
@ -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:
|
||||
|
|
|
@ -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/$',
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue