From ad14c1258c09bc2489c87d2afd247525c39b1699 Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Mon, 9 Jul 2012 15:47:38 +0200 Subject: [PATCH] [gen] Implemented a mechanism 'forgot password?'. --- gen/generator.py | 8 +++++ gen/mixins/ToolMixin.py | 67 ++++++++++++++++++++++++++++++++++++++++- gen/po.py | 15 +++++++++ gen/ui/appy.css | 1 + gen/ui/appy.js | 13 ++++++++ gen/ui/template.pt | 52 ++++++++++++++++++++++---------- 6 files changed, 139 insertions(+), 17 deletions(-) diff --git a/gen/generator.py b/gen/generator.py index f93ac6b..163e73c 100644 --- a/gen/generator.py +++ b/gen/generator.py @@ -487,6 +487,14 @@ class ZopeGenerator(Generator): msg('enable_cookies', '', msg.ENABLE_COOKIES), msg('page_previous', '', msg.PAGE_PREVIOUS), msg('page_next', '', msg.PAGE_NEXT), + msg('forgot_password', '', msg.FORGOT_PASSWORD), + msg('ask_password_reinit', '', msg.ASK_PASSWORD_REINIT), + msg('reinit_mail_sent', '', msg.REINIT_MAIL_SENT), + msg('reinit_password', '', msg.REINIT_PASSWORD), + msg('reinit_password_body', '', msg.REINIT_PASSWORD_BODY), + msg('new_password', '', msg.NEW_PASSWORD), + msg('new_password_body', '', msg.NEW_PASSWORD_BODY), + msg('new_password_sent', '', msg.NEW_PASSWORD_SENT), ] # Create a label for every role added by this application for role in self.getAllUsedRoles(): diff --git a/gen/mixins/ToolMixin.py b/gen/mixins/ToolMixin.py index 2cf7149..ad38edb 100644 --- a/gen/mixins/ToolMixin.py +++ b/gen/mixins/ToolMixin.py @@ -4,11 +4,12 @@ from appy.shared import mimeTypes from appy.shared.utils import getOsTempFolder, sequenceTypes from appy.shared.data import languages import appy.gen -from appy.gen import Type, Search, Selection +from appy.gen import Type, Search, Selection, String from appy.gen.utils import SomeObjects, getClassName from appy.gen.mixins import BaseMixin from appy.gen.wrappers import AbstractWrapper from appy.gen.descriptors import ClassDescriptor +from appy.gen.mail import sendMail try: from AccessControl.ZopeSecurityPolicy import _noroles except ImportError: @@ -1042,4 +1043,68 @@ class ToolMixin(BaseMixin): if hasattr(self.o.aq_base, 'pages') and self.o.pages: return [self.getObject(uid) for uid in self.o.pages ] return () + + def askPasswordReinit(self): + '''A user (anonymmous) does not remember its password. Here we will + send him a mail containing a link that will trigger password + re-initialisation.''' + login = self.REQUEST.get('login').strip() + appyTool = self.appy() + user = appyTool.search1('User', login=login, noSecurity=True) + msg = self.translate('reinit_mail_sent') + backUrl = self.REQUEST['HTTP_REFERER'] + if not user: + # Return the message nevertheless. This way, malicious users can't + # deduce information about existing users. + return self.goto(backUrl, msg) + # If login is an email, use it. Else, use user.email instead. + email = user.login + if not String.EMAIL.match(email): + email = user.email + if not email: + # Impossible to re-initialise the password. + return self.goto(backUrl, msg) + # Create a temporary file whose name is the user login and whose + # content is a generated token. + f = file(os.path.join(getOsTempFolder(), login), 'w') + token = String().generatePassword() + f.write(token) + f.close() + # Send an email + initUrl = '%s/doPasswordReinit?login=%s&token=%s' % \ + (self.absolute_url(), login, token) + subject = self.translate('reinit_password') + map = {'url':initUrl, 'siteUrl':self.getSiteUrl()} + body= self.translate('reinit_password_body', mapping=map, format='text') + sendMail(appyTool, email, subject, body) + return self.goto(backUrl, msg) + + def doPasswordReinit(self): + '''Performs the password re-initialisation.''' + rq = self.REQUEST + login = rq['login'] + token = rq['token'] + # Check if such token exists in temp folder + tokenFile = os.path.join(getOsTempFolder(), login) + if os.path.exists(tokenFile): + f = file(tokenFile) + storedToken = f.read() + f.close() + if storedToken == token: + # Generate a new password for this user + appyTool = self.appy() + user = appyTool.search1('User', login=login, noSecurity=True) + newPassword = user.setPassword() + # Send the new password by email + email = login + if not String.EMAIL.match(email): + email = user.email + subject = self.translate('new_password') + siteUrl = self.getSiteUrl() + map = {'password': newPassword, 'siteUrl': siteUrl} + body = self.translate('new_password_body', mapping=map, + format='text') + sendMail(appyTool, email, subject, body) + os.remove(tokenFile) + return self.goto(siteUrl, self.translate('new_password_sent')) # ------------------------------------------------------------------------------ diff --git a/gen/po.py b/gen/po.py index b7906da..62e71d9 100644 --- a/gen/po.py +++ b/gen/po.py @@ -136,6 +136,21 @@ class PoMessage: ENABLE_COOKIES = 'You must enable cookies before you can log in.' PAGE_PREVIOUS = 'Previous page' PAGE_NEXT = 'Next page' + FORGOT_PASSWORD = 'Forgot password?' + ASK_PASSWORD_REINIT = 'Ask new password' + REINIT_MAIL_SENT = 'A mail has been sent to you. Please follow the ' \ + 'instructions from this email.' + REINIT_PASSWORD = 'Password re-initialisation' + REINIT_PASSWORD_BODY = 'Hello,

A password re-initialisation ' \ + 'has been requested, tied to this email address, for the website ' \ + '${siteUrl}. If you are not at the origin of this request, please ' \ + 'ignore this email. Else, click on the link below to receive a new ' \ + 'password.

${url}' + NEW_PASSWORD = 'Your new password' + NEW_PASSWORD_BODY = 'Hello,

The new password you have ' \ + 'requested for website ${siteUrl} is ${password}
' \ + '
Best regards.' + NEW_PASSWORD_SENT = 'Your new password has been sent to you by email.' def __init__(self, id, msg, default, fuzzy=False, comments=[], niceDefault=False): diff --git a/gen/ui/appy.css b/gen/ui/appy.css index ae4ddf6..1f20487 100644 --- a/gen/ui/appy.css +++ b/gen/ui/appy.css @@ -67,6 +67,7 @@ img { border: 0; vertical-align: middle} border-radius: 2px 2px 2px 2px; box-shadow: 0 2px 4px #A9A9A9;} .focus td { padding: 4px 0px 4px 4px } .discreet { font-size: 90%; } +.lostPassword a { font-size: 90%; color: white; padding-left: 1em;} .portlet { width: 150px; border-right: 1px solid #5F7983;} .portletContent { margin: 9px; } .portletTitle { font-weight: bold; font-size: 110%; margin-bottom: 4px;} diff --git a/gen/ui/appy.js b/gen/ui/appy.js index 9f0444a..5baba0e 100644 --- a/gen/ui/appy.js +++ b/gen/ui/appy.js @@ -451,6 +451,19 @@ function doConfirm() { } } +var wrongTextInput = '#ff934a none'; +// Function triggered when the user ask password reinitialisation +function doAskPasswordReinit() { + // Check that the user has typed a login + var theForm = document.getElementById('askPasswordReinitForm'); + var login = theForm.login.value.replace(' ', ''); + if (!login) { theForm.login.style.background = wrongTextInput; } + else { + closePopup('askPasswordReinitPopup'); + theForm.submit(); + } +} + // Function that finally posts the edit form after the user has confirmed that // she really wants to post it. function postConfirmedEditForm() { diff --git a/gen/ui/template.pt b/gen/ui/template.pt index dca54d3..8c02cac 100644 --- a/gen/ui/template.pt +++ b/gen/ui/template.pt @@ -78,7 +78,7 @@
Popup for confirming an action - + Popup for reinitialing the password + The user strip @@ -108,23 +123,28 @@ The user login form for anonymous users - +   +   +   + + + + + +
-
+
+ - - - - + + + + -   -   -   - - - -
+
User info and controls for authenticated users