changeset 8093:8b47181750a8 stable

login: fix incorrect CSRF rejection of "Reset Your Password" form (Issue #350) htmlfill would remove the CSRF token from the form when substituting the query parameters, causing password reset to break. By default, htmlfill will clear all input fields that doesn't have a new "default" value provided. It could be fixed by setting force_defaults to False - see http://www.formencode.org/en/1.2-branch/modules/htmlfill.html . It could also be fixed by providing the CSRF token in the defaults to be substituted in the form. Instead, refactor password_reset_confirmation to have more explicitly safe handling of query parameters. Replace htmlfill with the usual template variables. The URLs are generated in kallithea/model/user.py send_reset_password_email() and should only contain email, timestamp (integer as digit string) and a hex token from get_reset_password_token() .
author Mads Kiilerich <mads@kiilerich.com>
date Thu, 09 Jan 2020 12:28:33 +0100
parents bbbfee57fb96
children 4e0442f914b9
files kallithea/controllers/login.py kallithea/templates/password_reset_confirmation.html kallithea/tests/functional/test_login.py
diffstat 3 files changed, 8 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/controllers/login.py	Thu Jan 09 21:54:27 2020 +0100
+++ b/kallithea/controllers/login.py	Thu Jan 09 12:28:33 2020 +0100
@@ -210,12 +210,10 @@
 
         # The template needs the email address outside of the form.
         c.email = request.params.get('email')
-
+        c.timestamp = request.params.get('timestamp') or ''
+        c.token = request.params.get('token') or ''
         if not request.POST:
-            return htmlfill.render(
-                render('/password_reset_confirmation.html'),
-                defaults=dict(request.params),
-                encoding='UTF-8')
+            return render('/password_reset_confirmation.html')
 
         form = PasswordResetConfirmationForm()()
         try:
--- a/kallithea/templates/password_reset_confirmation.html	Thu Jan 09 21:54:27 2020 +0100
+++ b/kallithea/templates/password_reset_confirmation.html	Thu Jan 09 12:28:33 2020 +0100
@@ -22,13 +22,13 @@
         ${h.form(h.url('reset_password_confirmation'), method='post')}
         <p>${_('You are about to set a new password for the email address %s.') % c.email}</p>
         <p>${_('Note that you must use the same browser session for this as the one used to request the password reset.')}</p>
-        ${h.hidden('email')}
-        ${h.hidden('timestamp')}
+        ${h.hidden('email', value=c.email)}
+        ${h.hidden('timestamp', value=c.timestamp)}
         <div class="form">
                 <div class="form-group">
                     <label class="control-label" for="token">${_('Code you received in the email')}:</label>
                     <div>
-                        ${h.text('token', class_='form-control')}
+                        ${h.text('token', value=c.token, class_='form-control')}
                     </div>
                 </div>
 
--- a/kallithea/tests/functional/test_login.py	Thu Jan 09 21:54:27 2020 +0100
+++ b/kallithea/tests/functional/test_login.py	Thu Jan 09 12:28:33 2020 +0100
@@ -459,12 +459,12 @@
         assert response.status == '200 OK'
         response.mustcontain("You are about to set a new password for the email address %s" % email)
         response.mustcontain('<form action="%s" method="post">' % url(controller='login', action='password_reset_confirmation'))
-        response.mustcontain(no='value="%s"' % self.session_csrf_secret_token())  # BUG making actual password_reset_confirmation POST fail
+        response.mustcontain('value="%s"' % self.session_csrf_secret_token())
         response.mustcontain('value="%s"' % token)
         response.mustcontain('value="%s"' % timestamp)
         response.mustcontain('value="username@example.com"')
 
-        # fake a submit of that form (*with* csrf token)
+        # fake a submit of that form
         response = self.app.post(url(controller='login',
                                      action='password_reset_confirmation'),
                                  {'email': email,