Commits

Thomas Waldmann  committed 3488b0e

forward port moin 1.9 password reset notification code, templates, docs

* add sphinx docs about password resetting
* fix RECOVERPASS_KEY KeyError if users tries to use same token twice
* add moin account-password options for mail subject and text, skipping
of accounts with already invalidated passwords
* change order of input fields in password recovery form, so the browser
will be able to find the right username/password fields to remember

  • Participants
  • Parent commits 1501cd1

Comments (0)

Files changed (10)

File MoinMoin/config/default.py

             SHOW_COMMENTS: False,
             WANT_TRIVIAL: False,
             ENC_PASSWORD: u'',  # empty value == invalid hash
+            RECOVERPASS_KEY: u'',  # empty value == invalid key
             SESSION_KEY: u'',  # empty value == invalid key
             DISABLED: False,
             BOOKMARKS: {},

File MoinMoin/script/account/resetpw.py

 """
 
 
+import sys
+
+from flask import current_app as app
 from flask.ext.script import Command, Option
 
 from MoinMoin.constants.keys import ITEMID, NAME, NAME_EXACT, EMAIL
     """raised if no such user exists"""
 
 
+class UserHasNoEMail(Fault):
+    """raised if user has no e-mail address in his profile"""
+
+
 class MailFailed(Fault):
     """raised if e-mail sending failed"""
 
 
-def set_password(uid, password, notify=False):
+def set_password(uid, password, notify=False, skip_invalid=False, subject=None, text=None):
     u = user.User(uid)
     if u and u.exists():
+        if skip_invalid and u.has_invalidated_password():
+            return
         u.set_password(password)
         u.save()
-        if notify and not u.disabled and u.email:
-            mailok, msg = u.mail_password_recovery()
+        if not u.email:
+            raise UserHasNoEMail('User profile does not have an E-Mail address (name: %r id: %r)!' % (u.name, u.id))
+        if notify and not u.disabled:
+            mailok, msg = u.mail_password_recovery(subject=subject, text=text)
             if not mailok:
                 raise MailFailed(msg)
     else:
                help='Notify user(s), send them an E-Mail with a password reset link.'),
         Option('--verbose', '-v', required=False, dest='verbose', action='store_true', default=False,
                help='Verbose operation'),
+        Option('--subject', required=False, dest='subject', type=unicode,
+               help='Subject text for the password reset notification E-Mail.'),
+        Option('--text', required=False, dest='text', type=unicode,
+               help='Template text for the password reset notification E-Mail. Default: use the builtin standard template'),
+        Option('--text-from-file', required=False, dest='text_file', type=unicode,
+               help='Read full template for the password reset notification E-Mail from the given file, overrides --text. Default: None'),
+        Option('--skip-invalid', required=False, dest='skip_invalid', action='store_true',
+               help='If a user\'s password hash is already invalid (pw is already reset), skip this user.'),
     )
 
-    def run(self, name, uid, password, all_users, notify, verbose):
+    def run(self, name, uid, password, all_users, notify, verbose, subject, text, text_file, skip_invalid):
         flags_given = name or uid or all_users
         if not flags_given:
             print 'incorrect number of arguments'
-            import sys
-            sys.exit()
+            sys.exit(1)
+
+        if notify and not app.cfg.mail_enabled:
+            print "This wiki is not enabled for mail processing. The --notify option requires this. Aborting..."
+            sys.exit(1)
+
+        if text_file:
+            with open(text_file) as f:
+                text = f.read().decode('utf-8')
 
         before_wiki()
         if uid:
             name = meta[NAME]
             email = meta[EMAIL]
             try:
-                set_password(uid, password, notify=notify)
-            except Fault, err:
+                set_password(uid, password, notify=notify, skip_invalid=skip_invalid,
+                             subject=subject, text=text)
+            except Fault as err:
                 status = "FAILURE: [%s]" % str(err)
             else:
                 status = "SUCCESS"

File MoinMoin/templates/recoverpass.html

 {{ gen.form.open(form, method="post", action=url_for('frontend.recoverpass')) }}
   {{ forms.render_errors(form) }}
   <dl>
+    {{ forms.render(form['token']) }}
     {{ forms.render(form['username']) }}
-    {{ forms.render(form['token']) }}
     {{ forms.render(form['password1']) }}
     {{ forms.render(form['password2']) }}
   </dl>

File MoinMoin/user.py

         # Invalidate all other browser sessions except this one.
         session['user.session_token'] = self.generate_session_token(False)
 
+    def has_invalidated_password(self):
+        """
+        Check if the password hash of this user is invalid.
+        """
+        return self.profile[ENC_PASSWORD] == ''
+
     def save(self, force=False):
         """
         Save user account data to user account file on disk.
         self.save()
         return True
 
-    def mail_password_recovery(self, cleartext_passwd=None):
+    def mail_password_recovery(self, cleartext_passwd=None, subject=None, text=None):
         """ Mail a user who forgot his password a message enabling
             him to login again.
         """
+        if not self.email:
+            return False, "user has no E-Mail address in his profile."
+
         token = self.generate_recovery_token()
 
-        text = _("""\
+        if subject is None:
+            subject = _('[%(sitename)s] Your wiki password recovery link', sitename='%(sitename)s')
+        subject = subject % dict(sitename=self._cfg.sitename or "Wiki")
+
+        if text is None:
+            text = _("""\
 Somebody has requested to email you a password recovery link.
 
 Please use the link below to change your password to a known value:
 %(link)s
 
 If you didn't forget your password, please ignore this email.
+""", link='%(link)s')
+        text = text % dict(link=url_for('frontend.recoverpass', username=self.name0, token=token, _external=True))
 
-""", link=url_for('frontend.recoverpass', username=self.name0, token=token, _external=True))
-
-        subject = _('[%(sitename)s] Your wiki password recovery link',
-                    sitename=self._cfg.sitename or "Wiki")
         mailok, msg = sendmail.sendmail(subject, text, to=[self.email], mail_from=self._cfg.mail_from)
         return mailok, msg
 

File docs/admin/password-reset.rst

+===============================
+Password Resetting/Invalidation
+===============================
+There might be circumstances when the wiki admin wants or needs to reset one
+user's or all users' password (hash).
+
+For example:
+
+* you had a security breach on your wiki server (or somewhere else) and the
+  old password hashes (or passwords) were exposed
+* you want to make sure some user or all users set a new password, e.g. if:
+
+  - your password policy has changed (requiring longer passwords for example)
+  - you changed your passlib configuration and want to immediately have all
+    hashes upgraded
+
+Note: if we say "reset a password" (to use a commonly used term), we mean to
+"invalidate the password hash" (so that no password exists that validates
+against that hash). MoinMoin does not keep user passwords in cleartext.
+
+The files we refer to below are located in docs/examples/password-reset/...
+
+
+Resetting one or few password(s)
+================================
+If you somehow interact with the users corresponding to the user accounts in
+question (by phone or directly), you don't need the extensive procedure as
+described below, just use::
+
+    moin account-password --name JoeDoe
+
+That will reset JoeDoe's password. Tell him to visit the login URL and use
+the "forgot my password" functionality to define a new password.
+
+If that doesn't work (e.g. if e-mail is not enabled for your wiki or he has
+a non-working e-mail address in his profile), you can also set a password for
+him::
+
+    moin account-password --name JoeDoe --password uIkV9.-a3
+
+Choose a rather complicated password to make sure they change it a minute
+afterwards (to another, hopefully safe password).
+
+
+Resetting many or all password(s)
+=================================
+If you have a lot of passwords to reset, you need a better procedure that
+avoids having to deal with too many users individually.
+
+
+Preparing your users
+--------------------
+Tell your users beforehands that you will be doing a password reset, otherwise
+they might find the automatically generated E-Mail they'll get suspicious and
+you'll have to explain it to them individually that the E-Mail is legitimate.
+
+Also, remind your users that having a valid E-Mail address in their user
+settings is essential for getting a password recovery E-Mail.
+
+If an active user does somehow not get such a mail, you likely will have to
+manually define a valid E-Mail address (or even password) for that user.
+
+
+Make sure E-Mail functionality works
+------------------------------------
+If you know you have working E-Mail functionality, skip this section.
+
+Password recovery and password reset notification work via E-Mail, so you
+should have it configured::
+
+    # the E-Mail address used for From: (consider using an address that
+    # can be directly replied to, at least while doing the pw reset):
+    mail_from = 'wiki@example.org'
+    # your smtp mail server hostname:port (default is 25)
+    mail_smarthost = 'mail.example.org:587'
+    # the login there, if authentication is needed
+    mail_username = 'wiki@example.org'
+    mail_password = 'SuperSecretSMTPPassword'
+
+You can try whether it works by using the "forgot my password" functionality
+on the login page.
+
+
+Editing mailtemplate.txt
+------------------------
+If you edit mailtemplate.txt, please be very careful and follow these rules
+(otherwise you might just see the script command crashing):
+
+The contents must be utf-8 (or ascii, which is a subset of utf-8).
+In case of doubt, just use plain English.
+
+Some places you likely should edit are marked with XXX.
+
+Do not use any % character in your text (except for the placeholders).
+If you need a verbatim % character, you need to write %%.
+
+It is a very good idea to give some URL (e.g. of a web or wiki page) in
+the text where users can read more information.
+
+Of course the information at that URL should be readable without requiring
+a wiki login (you just have invalidated his/her password!), so the user can
+get informed before clicking links he got from someone via E-Mail.
+
+We have added a wikitemplate.txt you can use to create such a wiki page.
+
+Instead of creating a web or wiki page with the information, you could
+also write all the stuff into the mail template directly, but please consider
+that E-Mail delivery to some users might fail for misc. reasons, so having
+some information on the web/wiki is usually better.
+
+
+Editing wikitemplate.txt
+------------------------
+Just copy & paste it to some public page in your wiki, e.g. "PasswordReset".
+
+Some places you likely should edit are marked with XXX.
+
+
+Doing the password reset
+------------------------
+Maybe first try it with a single user account::
+
+    moin account-password --name JoeDoe --notify --subject 'Wiki password reset' --text-from-file mailtemplate.txt
+
+Use some valid name, maybe a testing account of yourself. You should now have
+mail. If that worked ok, you can now do a global password reset for your wiki::
+
+    moin account-password --verbose --all-users --notify --subject 'Wiki password reset' --text-from-file mailtemplate.txt
+
+The subject may contain a placeholder for the sitename, which is useful for
+wiki farms (showing the builtin default here)::
+
+    '[%(sitename)s] Your wiki account data'
+

File docs/examples/password-reset/mailtemplate-de.txt

+Der Wiki-Administrator hat Ihr Passwort invalidiert und angefordert,
+Ihnen diese E-Mail zu senden, damit Sie ein neues Passwort setzen koennen.
+
+Bitte lesen Sie wichtige Informationen hierzu (sowie Hinweise zur
+Problembeseitigung und Wiki-Administrator-Kontaktinformationen) dort:
+
+(XXX hier die URL angeben XXX)
+
+
+Bitte besuchen Sie nun die unten angezeigte Passwort-Ruecksetz-URL und
+setzen Sie ein neues Passwort.
+
+%(link)s
+

File docs/examples/password-reset/mailtemplate.txt

+The wiki administrator has invalidated your wiki password and requested
+to send this E-Mail to you, so you can set a new one.
+
+Important information about this (including troubleshooting information and
+wiki administrator contact information) is available there, please read it:
+
+(XXX give URL here XXX)
+
+
+Now, please go to the password reset URL below and set a new password.
+
+%(link)s
+

File docs/examples/password-reset/wikitemplate-de.txt

+Hinweis: einige Stellen, die man editieren muss, sind mit XXX markiert.
+
+= (XXX Datum angeben) YYYY-MM-DD Passworte zurückgesetzt =
+
+Der Wiki-Administrator hat für alle Benutzerkonten dieses Wikis die Passwörter invalidiert (auf ungültig gesetzt).
+
+Sie müssen eine Passwort-Rücksetzung ausführen, um ein ''neues und anderes'' Passwort für Ihr Konto zu setzen.
+
+== Gründe für die Passwort-Invalidierung ==
+
+ * (XXX Grund angeben)
+ * (XXX Grund angeben)
+
+== Was Sie tun müssen ==
+=== Lesen Sie wichtige Informationen ===
+Bitte besuchen Sie diese URLs für weitere Informationen:
+ * http://moinmo.in/HowToHandleSecurityBreach/de (allgemeine Hinweise, unbedingt lesen!)
+ * http://moinmo.in/SecurityFixes/CVE-YYYY-NNNN (über den spezifischen Vorfall, wenn Sie mehr Informationen möchten) (XXX URL korrigieren)
+
+=== Ein neues Passwort setzen ===
+Sie sollten eine E-Mail (inklusive eines Links, um Ihr Passwort zurückzusetzen) erhalten haben. Lesen und befolgen Sie sie.
+
+=== Problembehandlung ===
+==== Ungültiges Token? ====
+Das Token hat eine begrenzte Gültigkeitsdauer.
+
+Wenn es "Token ist ungültig" sagt, müssen Sie einfach ein neues Token anfordern, siehe unten.
+
+==== Sie haben eine unerwartete E-Mail über ein unbekanntes Benutzerkonto erhalten? ====
+Wenn Sie eine E-Mail erhalten haben, die Sie auffordert, das Passwort ''für ein Ihnen unbekanntes Benutzerkonto'' zurückzusetzen:
+ignorieren Sie sie einfach, setzen Sie das Passwort ''nicht'' zurück
+
+Wahrscheinlich hat jemand (z.B. ein Spammer) Ihre E-Mail-Adresse missbraucht, als er ein Benutzerkonto angelegt hat.
+Das passiert oft, Sie können das auch in Ihrem E-Mail-Spam-Ordner sehen.
+
+==== Sie haben die E-Mail nicht erhalten? ====
+Wenn Sie die E-Mail noch nicht gesehen haben, prüfen Sie bitte Ihren Spam-Ordner (oder auch andere Ordner, wo E-Mails vom Wiki sein könnten).
+
+Wenn Sie die von uns an Sie gesandte E-Mail nicht finden können, können Sie alternativ auch die normale Passwort-Rücksetz-Funktion des Wikis benutzen, siehe unten.
+
+==== Ein neues Passwort-Rücksetz-Token erhalten ====
+ * gehen Sie zur Anmelde-Seite des Wiki
+ * klicken Sie auf den "Password vergessen?"-Link
+ * Dann:
+  * ''entweder'' Ihren Wiki-Benutzernamen eingeben (empfehlenswert, wenn Sie ihn wissen oder einfach herausfinden können)
+  * ''oder'' Ihre E-Mail-Addresse (die gleiche wie in Ihrem Wiki-Benutzerprofil - möglicherweise brauchen Sie mehrere Versuche, wenn Sie sich hier nicht sicher sind)
+ * prüfen Sie Ihren E-Mail-Eingang, Sie sollten nun eine E-Mail mit einem Passwort-Rücksetz-Link haben
+ * klicken Sie auf den Link, definieren Sie ein neues Passwort
+
+==== Brauchen Sie Hilfe? ====
+Wenn das nicht funktioniert hat, brauchen Sie vermutlich Hilfe durch einen Wiki-Administrator, bitte kontaktieren Sie:
+
+(XXX hier Name und E-Mail des Wiki-Administrators angeben XXX)
+
+In Ihrer E-Mail, geben Sie bitte Folgendes an:
+ * die Wiki-Adresse (URL)
+ * Ihren Wiki-Benutzernamen
+ * die E-Mail-Adresse, die Sie in Ihrem Wiki-Benutzerprofil verwendet haben
+ * falls diese E-Mail-Adresse nicht mehr funktioniert, teilen Sie uns das bitte mit
+
+== Fragen? ==
+Wenn Sie Fragen haben, die nicht bereits auf dieser oder den verlinkten Seiten beantwortet werden, fragen Sie den Wiki-Administrator.
+
+E-Mails mit Fragen, die dort bereits beantwortet sind, werden ignoriert.
+
+Anstelle Fragen individuell über E-Mail zu beantworten, können oft gestellte Fragen auch auf dieser Seite oder den verlinkten Seiten beantwortet werden.
+

File docs/examples/password-reset/wikitemplate.txt

+Note: some stuff that needs editing is marked with XXX.
+
+= (XXX give date) YYYY-MM-DD Password Reset =
+
+The wiki administrator has invalidated the passwords for all user accounts on this wiki.
+
+You need to do a password recovery to set a ''new and changed'' password for your account.
+
+== Reason(s) for the password invalidation ==
+
+ * (XXX give reason)
+ * (XXX give reason)
+
+== What you need to do ==
+=== Read important information ===
+Please visit these URLs for more information:
+ * http://moinmo.in/HowToHandleSecurityBreach ('''generic advice, MUST READ!''')
+ * http://moinmo.in/SecurityFixes/CVE-YYYY-NNNN (about the specific incident, if you want more information) (XXX fix url)
+
+=== Enter a new password ===
+You should have received an E-Mail (including a link to reset your password). Just read and follow it.
+
+==== Invalid token? ====
+The token has a limited lifetime.
+
+If it says "Token is invalid", you just need to get a new password recovery token, see below.
+
+==== You didn't get the E-Mail? ====
+If you didn't see that E-Mail yet, please check your spam folder (or other folders where mails from the wiki could be).
+
+If you can't find the E-Mail we sent to you, you can alternatively just use the normal password recovery function of the wiki, see below.
+
+==== Getting a new password recovery token ====
+ * go to the login page
+ * click the "Forgot your password?" link
+ * now:
+  * ''either'' enter your wiki username (recommended, if you still remember it or you can find out easily)
+  * ''or'' your E-Mail address (same as in your wiki user profile - you might need multiple tries if you are not sure about this)
+ * check your E-Mail, you should have one now with a password recovery link
+ * click on the link, define a new password
+
+==== Need help? ====
+If that didn't work, you will need help by a wiki administrator, please contact:
+
+(XXX give name and E-Mail address of wiki administrator here XXX)
+
+In your E-Mail, please give:
+ * the wiki address (URL)
+ * your wiki user name
+ * the E-Mail address that you used in your wiki userprofile
+ * if that E-Mail address is not working any more, please tell so
+
+== Questions? ==
+If you have any questions that are not answered by reading this page or the pages linked from here, ask the wiki administrator.
+
+E-Mails asking questions that ''are'' answered on these pages will be ignored.
+
+Instead of answering individually via E-Mail, frequently asked questions might might also get answered on this page or the pages linked from here.
+

File docs/index.rst

    admin/upgrade
    admin/backup
    admin/index
+   admin/password-reset
    man/moin
 
 Getting Support for and Contributing to MoinMoin