diff options
| author | Romain Garrigues <romain.garrigues@makina-corpus.com> | 2017-01-13 14:17:54 +0000 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2017-01-13 09:17:54 -0500 |
| commit | ede59ef6f39ff8a6443c2b24df0208ef6ec41ee0 (patch) | |
| tree | ee8c155dbc4520371e06fe3251e45e283fc5115d /django | |
| parent | 91023d79ec70df9289271e63a67675ee51e7dea8 (diff) | |
Fixed #27518 -- Prevented possibie password reset token leak via HTTP Referer header.
Thanks Florian Apolloner for contributing to this patch and
Collin Anderson, Markus Holtermann, and Tim Graham for review.
Diffstat (limited to 'django')
| -rw-r--r-- | django/contrib/auth/tokens.py | 2 | ||||
| -rw-r--r-- | django/contrib/auth/views.py | 29 |
2 files changed, 26 insertions, 5 deletions
diff --git a/django/contrib/auth/tokens.py b/django/contrib/auth/tokens.py index 46b8467476..6cf694cebb 100644 --- a/django/contrib/auth/tokens.py +++ b/django/contrib/auth/tokens.py @@ -24,6 +24,8 @@ class PasswordResetTokenGenerator(object): """ Check that a password reset token is correct for a given user. """ + if not (user and token): + return False # Parse the token try: ts_b36, hash = token.split("-") diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 8edf02a7d1..be156286c6 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -425,6 +425,10 @@ class PasswordResetView(PasswordContextMixin, FormView): return super(PasswordResetView, self).form_valid(form) +INTERNAL_RESET_URL_TOKEN = 'set-password' +INTERNAL_RESET_SESSION_TOKEN = '_password_reset_token' + + class PasswordResetDoneView(PasswordContextMixin, TemplateView): template_name = 'registration/password_reset_done.html' title = _('Password reset sent') @@ -446,12 +450,26 @@ class PasswordResetConfirmView(PasswordContextMixin, FormView): self.validlink = False self.user = self.get_user(kwargs['uidb64']) - if self.user is not None and self.token_generator.check_token(self.user, kwargs['token']): - self.validlink = True - else: - return self.render_to_response(self.get_context_data()) + if self.user is not None: + token = kwargs['token'] + if token == INTERNAL_RESET_URL_TOKEN: + session_token = self.request.session.get(INTERNAL_RESET_SESSION_TOKEN) + if self.token_generator.check_token(self.user, session_token): + # If the token is valid, display the password reset form. + self.validlink = True + return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs) + else: + if self.token_generator.check_token(self.user, token): + # Store the token in the session and redirect to the + # password reset form at a URL without the token. That + # avoids the possibility of leaking the token in the + # HTTP Referer header. + self.request.session[INTERNAL_RESET_SESSION_TOKEN] = token + redirect_url = self.request.path.replace(token, INTERNAL_RESET_URL_TOKEN) + return HttpResponseRedirect(redirect_url) - return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs) + # Display the "Password reset unsuccessful" page. + return self.render_to_response(self.get_context_data()) def get_user(self, uidb64): try: @@ -471,6 +489,7 @@ class PasswordResetConfirmView(PasswordContextMixin, FormView): user = form.save() if self.post_reset_login: auth_login(self.request, user) + del self.request.session[INTERNAL_RESET_SESSION_TOKEN] return super(PasswordResetConfirmView, self).form_valid(form) def get_context_data(self, **kwargs): |
