Début d'implémentation du reset des mots de passe.
authorBenoît Pin <benoit.pin@gmail.com>
Sun, 23 Jun 2013 23:49:19 +0000 (01:49 +0200)
committerBenoît Pin <benoit.pin@gmail.com>
Sun, 23 Jun 2013 23:49:19 +0000 (01:49 +0200)
RegistrationTool.py
skins/custom_generic/login_form.pt
skins/generic/request_password_reset_form.pt [new file with mode: 0644]

index 77f8bdc..7d2f29b 100644 (file)
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 #######################################################################################
 #   Plinn - http://plinn.org                                                          #
-#   Copyright (C) 2005-2007  Benoît PIN <benoit.pin@ensmp.fr>                         #
+#   © 2005-2013  Benoît PIN <pin@cri.ensmp.fr>                                        #
 #                                                                                     #
 #   This program is free software; you can redistribute it and/or                     #
 #   modify it under the terms of the GNU General Public License                       #
@@ -17,7 +17,7 @@
 #   along with this program; if not, write to the Free Software                       #
 #   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.   #
 #######################################################################################
-""" Plinn registration tool: implements 3 modes to register members :
+""" Plinn registration tool: implements 3 modes to register members:
        anonymous, manager, reviewed.
 
 
@@ -29,11 +29,16 @@ from Products.PageTemplates.PageTemplateFile import PageTemplateFile
 from Products.CMFDefault.RegistrationTool import RegistrationTool as BaseRegistrationTool
 from AccessControl import ClassSecurityInfo, ModuleSecurityInfo
 from AccessControl.Permission import Permission
+from BTrees.OOBTree import OOBTree
 from Products.CMFCore.permissions import ManagePortal, AddPortalMember
 from Products.CMFCore.exceptions import AccessControl_Unauthorized
 from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.utils import getUtilityByInterfaceName
 from Products.GroupUserFolder.GroupsToolPermissions import ManageGroups
+from Products.Plinn.utils import Message as _
+from DateTime import DateTime
 from types import TupleType, ListType
+from uuid import uuid4
 
 security = ModuleSecurityInfo('Products.Plinn.RegistrationTool')
 MODE_ANONYMOUS = 'anonymous'
@@ -72,6 +77,7 @@ class RegistrationTool(BaseRegistrationTool) :
        def __init__(self) :
                self._mode = MODE_ANONYMOUS
                self._chain = ''
+               self._passwordResetRequests = OOBTree()
        
        security.declareProtected(ManagePortal, 'configureTool')
        def configureTool(self, registration_mode, chain, REQUEST=None) :
@@ -183,5 +189,52 @@ class RegistrationTool(BaseRegistrationTool) :
                """ notify member creation """
                member.notifyWorkflowCreated()
                member.indexObject()
+       
+
+       security.declarePublic('requestPasswordReset')
+       def requestPasswordReset(self, userid):
+               """ add uuid / (userid, expiration) pair and return uuid """
+               self.clearExpiredPasswordResetRequests()
+               mtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
+               if mtool.getMemberById(userid) :
+                       uuid = str(uuid4())
+                       self._passwordResetRequests[uuid] = (userid, DateTime() + 1)
+                       return uuid
+       
+       security.declarePrivate('clearExpiredPasswordResetRequests')
+       def clearExpiredPasswordResetRequests(self):
+               now = DateTime()
+               for uuid, record in self._passwordResetRequest.items() :
+                       userid, date = record
+                       if date < now :
+                               del self._passwordResetRequests[uuid]
+       
+       
+       security.declarePublic('resetPassword')
+       def resetPassword(self, userid, uuid, password, confirm) :
+               record = self._passwordResetRequests.get(uuid)
+               if not record :
+                       return _('Invalid reset password request.')
+               
+               recUserid, expiration = record
+               
+               if recUserid != userid :
+                       return _('Invalid userid.')
+               
+               if expiration < now :
+                       self.clearExpiredPasswordResetRequests()
+                       return _('Your reset password request has expired. You can ask a new one.')
+               
+               msg = self.testPasswordValidity(password, confirm=confirm)
+               if not msg : # None if everything ok. Err message otherwise.
+                       mtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
+                       member = mtool.getMemberById(userid)
+                       if member :
+                               member.setSecurityProfile(password=password)
+                               del self._passwordResetRequests[uuid]
+                               return _('Password successfully resetted.')
+                       else :
+                               return _('"%s" username not found.') % userid
+                       
                
 InitializeClass(RegistrationTool)
\ No newline at end of file
index 3d3c4bb..0ebaefc 100644 (file)
@@ -1,81 +1,66 @@
 <html xmlns:tal="http://xml.zope.org/namespaces/tal"
       xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
       metal:use-macro="here/main_template/macros/master">
   <head>
     <meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
   </head>
-<body>
-<div metal:fill-slot="main_no_tabs" i18n:domain="cmf_default">
-<div class="Desktop">
-<h1 class="Desktop" i18n:translate="">Log in</h1>
-
-<form action="" method="post" tal:attributes="action string:${here/portal_url}/logged_in">
-<input type="hidden" name="noAjax" value="1" />
-
-<!-- ****** Enable the automatic redirect ***** -->
-<span tal:condition="exists: request/came_from">
-  <input type="hidden" name="came_from" value=""
-         tal:attributes="value request/came_from" />
-</span>
-<!-- ****** Enable the automatic redirect ***** -->
-<input type="hidden" name="just_login" value="True" />
-<table class="FormLayout">
-<tr>
-  <td align="left" valign="top">
-                <strong i18n:translate="user_name">Login</strong>
+  <body>
+    <div metal:fill-slot="main_no_tabs" i18n:domain="cmf_default">
+      <div id="Desktop">
+        <h1 i18n:translate="">Log in</h1>
+        <form action="" method="post" tal:attributes="action string:${here/portal_url}/logged_in">
+          <input type="hidden" name="noAjax" value="1"/>
+          <!-- ****** Enable the automatic redirect ***** -->
+          <span tal:condition="exists: request/came_from">
+            <input type="hidden" name="came_from" value="" tal:attributes="value request/came_from"/>
+          </span>
+          <!-- ****** Enable the automatic redirect ***** -->
+          <input type="hidden" name="just_login" value="True"/>
+          <table class="TwoColumnForm">
+            <tr>
+              <th i18n:translate="user_name">Login</th>
+              <td>
+                <input type="text" name="__ac_name" size="20" value="" tal:attributes="value python: request.get('__ac_name') or ''"/>
               </td>
-  <td align="left" valign="top">
-  <input type="text" name="__ac_name" size="20" value=""
-         tal:attributes="value python: request.get('__ac_name') or ''" />
-  </td>
-</tr>
-<tr>
-  <td align="left" valign="top">
-  <strong i18n:translate="">Password</strong>
-  </td>
-  <td align="left" valign="top">
-  <input type="password" name="__ac_password" size="20" />
-  </td>
-</tr>
-
-<tr valign="top" align="left">
-<td></td>
-<td><input type="checkbox" name="__ac_persistent" value="1" checked="checked"
-      id="cb_remember" />
-<label for="cb_remember" i18n:translate="">Remember my name.</label>
-</td></tr>
-
-<tr>
-  <td align="left" valign="top">
-  </td>
-  <td align="left" valign="top">
-  <input type="submit" name="submit" value=" Login "
-         i18n:attributes="value" />
-  </td>
-</tr>
-
-</table>
-</form>
-
-<p><a href=""
-    tal:attributes="href string:${here/portal_url}/mail_password_form"
-    i18n:translate=""
-   >Forgot your password?</a>
-</p>
-
-<p i18n:translate="">Having trouble logging in? Make sure to enable cookies in
-    your web browser.
-</p>
-<p i18n:translate="">Don't forget to logout or exit your browser when you're
-  done.
-</p>
-
-<p i18n:translate="">Setting the 'Remember my name' option will set a cookie
-  with your username, so that when you next log in, your user name will
-  already be filled in for you.
-</p>
-</div>
-</div>
-</body>
+            </tr>
+            <tr>
+              <th i18n:translate="">Password</th>
+              <td>
+                <input type="password" name="__ac_password" size="20"/>
+              </td>
+            </tr>
+            <tr>
+              <td><br/></td>
+              <td>
+                <input type="checkbox" name="__ac_persistent" value="1" checked="checked" id="cb_remember"/>
+                <label for="cb_remember" i18n:translate="">Remember my name.</label>
+              </td>
+            </tr>
+            <tr>
+              <td><br/></td>
+              <td>
+                <input type="submit" name="submit" value=" Login " i18n:attributes="value"/>
+              </td>
+            </tr>
+          </table>
+        </form>
+        <!-- <p>
+          <a href="" tal:attributes="href string:${here/portal_url}/request_password_reset_form" i18n:translate="">Forgot your password?</a>
+        </p> -->
+        <p i18n:translate="">
+          Having trouble logging in? Make sure to enable cookies in your web
+          browser.
+        </p>
+        <p i18n:translate="">
+          Don't forget to logout or exit your browser when you're done.
+        </p>
+        <p i18n:translate="">
+          Setting the 'Remember my name' option will set a cookie with your
+          username, so that when you next log in, your user name will already be
+          filled in for you.
+        </p>
+      </div>
+    </div>
+  </body>
 </html>
-
diff --git a/skins/generic/request_password_reset_form.pt b/skins/generic/request_password_reset_form.pt
new file mode 100644 (file)
index 0000000..fe7c384
--- /dev/null
@@ -0,0 +1,30 @@
+<html xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+      metal:use-macro="here/main_template/macros/master">
+  <head>
+    <meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
+  </head>
+  <body>
+    <div metal:fill-slot="main_no_tabs" i18n:domain="plinn">
+      <div id="Desktop" tal:define="ptool here/portal_properties">
+        <h1 i18n:translate="">Password reset</h1>
+        <p i18n:translate="">
+          Enter your username below and click on the "Send" button. You will
+          receive an email with a link to reset your password.
+        </p>
+        <p i18n:translate="">
+          If this will not work for you (for example, if you forget your
+          username or didn't enter your email address) send email to <a
+          tal:attributes="href string:mailto:${ptool/email_from_address}"
+          tal:content="ptool/email_from_address" href="mailto:me@here.com"
+          i18n:name="admin_email">me@here.com</a>.
+        </p>
+        <form action="mail_password" tal:attributes="action string:${here/portal_url}/mail_password">
+          <input name="userid"/>
+          <input type="submit" value=" Send " i18n:attributes="value"/>
+        </form>
+      </div>
+    </div>
+  </body>
+</html>