From e2575a5f1e9aab749c37ecc053333c5a606b9f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Calonne?= <aurel@nexedi.com> Date: Fri, 25 Jan 2008 16:35:13 +0000 Subject: [PATCH] add a password tool wich allow a user to change its password it he lost his password git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@18858 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5/ERP5Site.py | 2 + product/ERP5/Tool/PasswordTool.py | 200 ++++++++++++++++++++++++++++++ product/ERP5/__init__.py | 3 +- 3 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 product/ERP5/Tool/PasswordTool.py diff --git a/product/ERP5/ERP5Site.py b/product/ERP5/ERP5Site.py index 4d40fd4338..0fa885c0db 100644 --- a/product/ERP5/ERP5Site.py +++ b/product/ERP5/ERP5Site.py @@ -1194,6 +1194,8 @@ class ERP5Generator(PortalGenerator): addTool('ERP5 Order Tool', None) if not p.hasObject('portal_tests'): addTool('ERP5 Test Tool', None) + if not p.hasObject('portal_password'): + addTool('ERP5 Password Tool', None) # Add ERP5Type Tool addTool = p.manage_addProduct['ERP5Type'].manage_addTool diff --git a/product/ERP5/Tool/PasswordTool.py b/product/ERP5/Tool/PasswordTool.py new file mode 100644 index 0000000000..cc789b4825 --- /dev/null +++ b/product/ERP5/Tool/PasswordTool.py @@ -0,0 +1,200 @@ +############################################################################## +# +# Copyright (c) 2008 Nexedi SARL and Contributors. All Rights Reserved. +# Aurelien Calonne <aurel@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + + +from AccessControl import ClassSecurityInfo +from Globals import InitializeClass, DTMLFile, get_request +from Products.ERP5Type.Tool.BaseTool import BaseTool +from Products.ERP5Type import Permissions +from Products.ERP5 import _dtmldir +from zLOG import LOG +import time, random, md5 +from DateTime import DateTime +from Products.ERP5Type.Message import Message +from Acquisition import aq_base +from BTrees.OOBTree import OOBTree +N_ = lambda msgid, **kw: Message('ui', msgid, **kw) + +class PasswordTool(BaseTool): + """ + PasswoordTool is used to allow a user to change its password + """ + title = 'Password Tool' + id = 'portal_password' + meta_type = 'ERP5 Password Tool' + portal_type = 'Password Tool' + allowed_types = () + + # Declarative Security + security = ClassSecurityInfo() + + security.declareProtected(Permissions.ManagePortal, 'manage_overview' ) + manage_overview = DTMLFile( 'explainPasswordTool', _dtmldir ) + + + _expiration_day = 1 + password_request_dict = {} + + def __init__(self): + self.password_request_dict = OOBTree() + + def mailPasswordResetRequest(self, user_login=None, REQUEST=None): + """ + Create a ramdom string and expiration date for request + """ + if user_login is None: + user_login = REQUEST["user_login"] + + # check user exists + user_list = self.portal_catalog.unrestrictedSearchResults(portal_type="Person", reference=user_login) + if len(user_list) == 0: + msg = N_("User ${user} doesn't exist.", + mapping={'user': user_login}) + if REQUEST is not None: + ret_url = '%s/login_form?portal_status_message=%s' % \ + (self.getPortalObject().absolute_url(),msg) + return REQUEST.RESPONSE.redirect( ret_url ) + else: + return msg + + user = user_list[0].getObject() + # generate a ramdom string + random_url = self._generateUUID() + url = "%s/portal_password/resetPassword?key=%s" %(self.getPortalObject().absolute_url() , random_url) + # generate expiration date + expiration_date = DateTime() + self._expiration_day + # register request + self.password_request_dict = {random_url : (user_login, expiration_date)} + + # send mail + subject = "[%s] Reset of your password" %(self.getPortalObject().getTitle()) + message = "\nYou requested to reset your %s account password.\n\n" \ + "Please copy and paste the following link into your browser: \n%s\n\n" \ + "Please note that this link will be valid only one time, until %s.\n" \ + "After this date, or after having used this link, you will have to make " \ + "a new request\n\n" \ + "Thank you" %(self.getPortalObject().getTitle(), url, expiration_date) + self.portal_notifications.sendMessage(sender=None, recipient=[user,], subject=subject, message=message) + if REQUEST is not None: + msg = N_("An email has been sent to you.") + ret_url = '%s/login_form?portal_status_message=%s' % \ + (self.getPortalObject().absolute_url(),msg) + return REQUEST.RESPONSE.redirect( ret_url ) + + + def _generateUUID(self, args=""): + """ + Generate a unique id that will be used as url for password + """ + # this code is based on + # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/213761 + # by Carl Free Jr + # as uuid module is only available in pyhton 2.5 + t = long( time.time() * 1000 ) + r = long( random.random()*100000000000000000L ) + try: + a = socket.gethostbyname( socket.gethostname() ) + except: + # if we can't get a network address, just imagine one + a = random.random()*100000000000000000L + data = str(t)+' '+str(r)+' '+str(a)+' '+str(args) + data = md5.md5(data).hexdigest() + return data + + + def resetPassword(self, key=None, REQUEST=None): + """ + """ + if REQUEST is None: + REQUEST = get_request() + user_login, expiration_date = self.password_request_dict.get(key, (None, None)) + if key is None or user_login is None: + ret_url = '%s/login_form' % self.getPortalObject().absolute_url() + return REQUEST.RESPONSE.redirect( ret_url ) + + # check date + current_date = DateTime() + if current_date > expiration_date: + msg = N_("Date has expire.",) + ret_url = '%s/login_form?portal_status_message=%s' % \ + (self.getPortalObject().absolute_url(), msg) + return REQUEST.RESPONSE.redirect( ret_url ) + + # redirect to form as all is ok + REQUEST.set("password_key", key) + return self.getPortalObject().reset_password_form(REQUEST=REQUEST) + + + def removeExpiredRequests(self, **kw): + """ + Browse dict and remove expired request + """ + current_date = DateTime() + for key, (login, date) in self.password_request_dict.items(): + if date < current_date: + self.password_request_dict.pop(key) + + + def changeUserPassword(self, user_login, password, password_confirmation, password_key, REQUEST=None): + """ + Reset the password for a given login + """ + # check the key + register_user_login, expiration_date = self.password_request_dict.get(password_key, (None, None)) + + current_date = DateTime() + msg = None + if register_user_login is None: + msg = "" + elif register_user_login != user_login: + msg = N_("Bad login provided.",) + elif current_date > expiration_date: + msg = N_("Date has expire.",) + elif password != password_confirmation: + msg = N_("Password are not identical.",) + if msg is not None: + if REQUEST is not None: + ret_url = '%s/login_form?portal_status_message=%s' % \ + (self.getPortalObject().absolute_url(), msg) + return REQUEST.RESPONSE.redirect( ret_url ) + else: + return msg + + # all is OK, change password and remove it from request dict + self.password_request_dict.pop(password_key) + persons = self.acl_users.erp5_users.getUserByLogin(user_login) + person = persons[0] + person._setPassword(password) + person.reindexObject() + if REQUEST is not None: + msg = N_("Password changed.",) + ret_url = '%s/login_form?portal_status_message=%s' % \ + (self.getPortalObject().absolute_url(), msg) + return REQUEST.RESPONSE.redirect( ret_url ) + +InitializeClass(PasswordTool) diff --git a/product/ERP5/__init__.py b/product/ERP5/__init__.py index 6cfa4710c7..b9b1b2efa5 100644 --- a/product/ERP5/__init__.py +++ b/product/ERP5/__init__.py @@ -46,7 +46,7 @@ product_path = package_home( globals() ) # Define object classes and tools from Tool import CategoryTool, SimulationTool, RuleTool, IdTool, TemplateTool,\ TestTool, DomainTool, AlarmTool, OrderTool, DeliveryTool,\ - TrashTool, ContributionTool, NotificationTool + TrashTool, ContributionTool, NotificationTool, PasswordTool import ERP5Site object_classes = ( ERP5Site.ERP5Site, ) @@ -61,6 +61,7 @@ portal_tools = ( CategoryTool.CategoryTool, OrderTool.OrderTool, DeliveryTool.DeliveryTool, TrashTool.TrashTool, + PasswordTool.PasswordTool, ContributionTool.ContributionTool, NotificationTool.NotificationTool, ) -- 2.30.9