changeset 3564:474f6ad01900

recoverpass: email password reset token rather than sha1 login: no longer accept sha-1 encoded password directly
author Johannes Berg <johannes AT sipsolutions DOT net>
date Thu, 24 Apr 2008 16:05:04 +0200
parents 8140f31ada6d
children 98000f9de890
files MoinMoin/action/recoverpass.py MoinMoin/user.py
diffstat 2 files changed, 132 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/action/recoverpass.py	Thu Apr 24 15:20:15 2008 +0200
+++ b/MoinMoin/action/recoverpass.py	Thu Apr 24 16:05:04 2008 +0200
@@ -90,15 +90,115 @@
     return unicode(ret)
 
 
+def _create_token_form(request, name=None, token=None):
+    _ = request.getText
+    url = request.page.url(request)
+    ret = html.FORM(action=url)
+    ret.append(html.INPUT(type='hidden', name='action', value='recoverpass'))
+    lang_attr = request.theme.ui_lang_attr()
+    ret.append(html.Raw('<div class="userpref"%s>' % lang_attr))
+    tbl = html.TABLE(border="0")
+    ret.append(tbl)
+    ret.append(html.Raw('</div>'))
+
+    row = html.TR()
+    tbl.append(row)
+    row.append(html.TD().append(html.STRONG().append(html.Text(_("Username")))))
+    value = name or ''
+    row.append(html.TD().append(html.INPUT(type='text', size="36",
+                                           name="name", value=value)))
+
+    row = html.TR()
+    tbl.append(row)
+    row.append(html.TD().append(html.STRONG().append(html.Text(_("Recovery token")))))
+    value = token or ''
+    row.append(html.TD().append(html.INPUT(type='text', size="36",
+                                           name="token", value=value)))
+
+    row = html.TR()
+    tbl.append(row)
+    row.append(html.TD().append(html.STRONG().append(html.Text(_("New password")))))
+    row.append(html.TD().append(html.INPUT(type="password", size="36",
+                                           name="password")))
+
+    row = html.TR()
+    tbl.append(row)
+    row.append(html.TD().append(html.STRONG().append(html.Text(_("New password (repeat)")))))
+    row.append(html.TD().append(html.INPUT(type="password", size="36",
+                                           name="password_repeat")))
+
+    row = html.TR()
+    tbl.append(row)
+    row.append(html.TD())
+    td = html.TD()
+    row.append(td)
+    td.append(html.INPUT(type="submit", name="recover", value=_('Reset my password')))
+
+    return unicode(ret)
+
+
 def execute(pagename, request):
     pagename = pagename
     page = Page(request, pagename)
     _ = request.getText
     form = request.form
 
+    if not request.cfg.mail_enabled:
+        request.theme.add_msg(_("""This wiki is not enabled for mail processing.
+Contact the owner of the wiki, who can enable email."""), 'warning')
+        page.send_page()
+        return
+
     submitted = form.get('account_sendmail', [''])[0]
+    token = form.get('token', [''])[0]
+    newpass = form.get('password', [''])[0]
+    name = form.get('name', [''])[0]
 
-    if submitted: # user pressed create button
+    if token and name and newpass:
+        newpass2 = form.get('password_repeat', [''])[0]
+        msg = _("Passwords don't match!")
+        msg_type = 'error'
+        if newpass == newpass2:
+            pw_checker = request.cfg.password_checker
+            pw_error = None
+            if pw_checker:
+                pw_error = pw_checker(name, newpass)
+                if pw_error:
+                    msg = _("Password not acceptable: %s") % pw_error
+            if not pw_error:
+                u = user.User(request, user.getUserId(request, name))
+                if u and u.valid and token == u.recoverpass_token:
+                    u.enc_password = user.encodePassword(newpass)
+                    u.recoverpass_token = ''
+                    u.save()
+                    msg = _("Your password has been changed, you can log in now.")
+                    msg_type = 'info'
+                else:
+                    msg = _('Your token is invalid!')
+        if msg:
+            request.theme.add_msg(msg, msg_type)
+        if msg_type != 'error':
+            page.send_page()
+            return
+
+    if token and name:
+        request.emit_http_headers()
+        request.theme.send_title(_("Password reset"), pagename=pagename)
+
+        request.write(request.formatter.startContent("content"))
+
+        request.write(_("""
+== Password reset ==
+Enter a new password below.""", wiki=True))
+        request.write(_create_token_form(request, name=name, token=token))
+
+        request.write(request.formatter.endContent())
+
+        request.theme.send_footer(pagename)
+        request.theme.send_closing_html()
+    elif submitted: # user pressed create button
+        if request.request_method != 'POST':
+            return
         msg = _do_recover(request)
         request.theme.add_msg(msg, "dialog")
         page.send_page()
@@ -108,17 +208,20 @@
 
         request.write(request.formatter.startContent("content"))
 
-        if not request.cfg.mail_enabled:
-            request.write(_("""This wiki is not enabled for mail processing.
-Contact the owner of the wiki, who can enable email."""))
-        else:
-            request.write(_("""
+        request.write(_("""
 == Recovering a lost password ==
 If you have forgotten your password, provide your email address or username and click on '''Mail me my account data'''.
 The email you get contains the encrypted password (so even if someone intercepts the mail, he won't know your REAL password). Just copy and paste it into the login mask into the password field and log in.
 After logging in you should change your password.""", wiki=True))
 
-            request.write(_create_form(request))
+        request.write(_create_form(request))
+
+        request.write(_("""
+=== Password reset ===
+If you already have received the email with the recovery token, enter your
+username, the recovery token and a new password (twice) below.""", wiki=True))
+
+        request.write(_create_token_form(request))
 
         request.write(request.formatter.endContent())
 
--- a/MoinMoin/user.py	Thu Apr 24 15:20:15 2008 +0200
+++ b/MoinMoin/user.py	Thu Apr 24 16:05:04 2008 +0200
@@ -20,12 +20,13 @@
 """
 
 # add names here to hide them in the cgitb traceback
-unsafe_names = ("id", "key", "val", "user_data", "enc_password")
+unsafe_names = ("id", "key", "val", "user_data", "enc_password", "recoverpass_token")
 
 import os, time, sha, codecs
 
 from MoinMoin import config, caching, wikiutil, i18n, events
-from MoinMoin.util import timefuncs, filesys
+from MoinMoin.util import timefuncs, filesys, random_string
+from MoinMoin.wikiutil import url_quote_plus
 
 
 def getUserList(request):
@@ -312,15 +313,14 @@
         for key, label in self._cfg.user_checkbox_fields:
             setattr(self, key, self._cfg.user_checkbox_defaults.get(key, 0))
 
+        self.recoverpass_token = ""
+
         self.enc_password = ""
         if password:
-            if password.startswith('{SHA}'):
-                self.enc_password = password
-            else:
-                try:
-                    self.enc_password = encodePassword(password)
-                except UnicodeError:
-                    pass # Should never happen
+            try:
+                self.enc_password = encodePassword(password)
+            except UnicodeError:
+                pass # Should never happen
 
         #self.edit_cols = 80
         self.tz_offset = int(float(self._cfg.tz_offset) * 3600)
@@ -1025,37 +1025,29 @@
         from MoinMoin.wikiutil import getLocalizedPage
         _ = self._request.getText
 
-        if not self.enc_password: # generate pw if there is none yet
-            from random import randint
-            import base64
-
-            charset = 'utf-8'
-            pwd = "%s%d" % (str(time.time()), randint(0, 65535))
-            pwd = pwd.encode(charset)
-
-            pwd = sha.new(pwd).digest()
-            pwd = '{SHA}%s' % base64.encodestring(pwd).rstrip()
-
-            self.enc_password = pwd
+        if not self.recoverpass_token:
+            self.recoverpass_token = random_string(32, "abcdefghijklmnopqrstuvwxyz0123456789")
             self.save()
 
         text = '\n' + _("""\
 Login Name: %s
 
-Login Password: %s
+Password recovery token: %s
 
-Login URL: %s/?action=login
+Password reset URL: %s/?action=recoverpass&name=%s&token=%s
 """) % (
-                        self.name, self.enc_password, self._request.getBaseURL(), )
+                        self.name,
+                        self.recoverpass_token,
+                        self._request.getBaseURL(),
+                        url_quote_plus(self.name),
+                        self.recoverpass_token, )
 
         text = _("""\
-Somebody has requested to submit your account data to this email address.
+Somebody has requested to email you a password recovery token.
 
-If you lost your password, please use the data below and just enter the
-password AS SHOWN into the wiki's password form field (use copy and paste
-for that).
-
-After successfully logging in, it is of course a good idea to set a new and known password.
+If you lost your password, please go to the password reset URL below or
+go to the password recovery page again and enter your username and the
+recovery token.
 """) + text