[gen] Implemented a mechanism 'forgot password?'.

This commit is contained in:
Gaetan Delannay 2012-07-09 15:47:38 +02:00
parent 8a5ca81746
commit ad14c1258c
6 changed files with 139 additions and 17 deletions

View file

@ -487,6 +487,14 @@ class ZopeGenerator(Generator):
msg('enable_cookies', '', msg.ENABLE_COOKIES), msg('enable_cookies', '', msg.ENABLE_COOKIES),
msg('page_previous', '', msg.PAGE_PREVIOUS), msg('page_previous', '', msg.PAGE_PREVIOUS),
msg('page_next', '', msg.PAGE_NEXT), 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 # Create a label for every role added by this application
for role in self.getAllUsedRoles(): for role in self.getAllUsedRoles():

View file

@ -4,11 +4,12 @@ from appy.shared import mimeTypes
from appy.shared.utils import getOsTempFolder, sequenceTypes from appy.shared.utils import getOsTempFolder, sequenceTypes
from appy.shared.data import languages from appy.shared.data import languages
import appy.gen 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.utils import SomeObjects, getClassName
from appy.gen.mixins import BaseMixin from appy.gen.mixins import BaseMixin
from appy.gen.wrappers import AbstractWrapper from appy.gen.wrappers import AbstractWrapper
from appy.gen.descriptors import ClassDescriptor from appy.gen.descriptors import ClassDescriptor
from appy.gen.mail import sendMail
try: try:
from AccessControl.ZopeSecurityPolicy import _noroles from AccessControl.ZopeSecurityPolicy import _noroles
except ImportError: except ImportError:
@ -1042,4 +1043,68 @@ class ToolMixin(BaseMixin):
if hasattr(self.o.aq_base, 'pages') and self.o.pages: if hasattr(self.o.aq_base, 'pages') and self.o.pages:
return [self.getObject(uid) for uid in self.o.pages ] return [self.getObject(uid) for uid in self.o.pages ]
return () 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'))
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -136,6 +136,21 @@ class PoMessage:
ENABLE_COOKIES = 'You must enable cookies before you can log in.' ENABLE_COOKIES = 'You must enable cookies before you can log in.'
PAGE_PREVIOUS = 'Previous page' PAGE_PREVIOUS = 'Previous page'
PAGE_NEXT = 'Next 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,<br/><br/>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.<br/><br/>${url}'
NEW_PASSWORD = 'Your new password'
NEW_PASSWORD_BODY = 'Hello,<br/><br/>The new password you have ' \
'requested for website ${siteUrl} is ${password}<br/>' \
'<br/>Best regards.'
NEW_PASSWORD_SENT = 'Your new password has been sent to you by email.'
def __init__(self, id, msg, default, fuzzy=False, comments=[], def __init__(self, id, msg, default, fuzzy=False, comments=[],
niceDefault=False): niceDefault=False):

View file

@ -67,6 +67,7 @@ img { border: 0; vertical-align: middle}
border-radius: 2px 2px 2px 2px; box-shadow: 0 2px 4px #A9A9A9;} border-radius: 2px 2px 2px 2px; box-shadow: 0 2px 4px #A9A9A9;}
.focus td { padding: 4px 0px 4px 4px } .focus td { padding: 4px 0px 4px 4px }
.discreet { font-size: 90%; } .discreet { font-size: 90%; }
.lostPassword a { font-size: 90%; color: white; padding-left: 1em;}
.portlet { width: 150px; border-right: 1px solid #5F7983;} .portlet { width: 150px; border-right: 1px solid #5F7983;}
.portletContent { margin: 9px; } .portletContent { margin: 9px; }
.portletTitle { font-weight: bold; font-size: 110%; margin-bottom: 4px;} .portletTitle { font-weight: bold; font-size: 110%; margin-bottom: 4px;}

View file

@ -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 // Function that finally posts the edit form after the user has confirmed that
// she really wants to post it. // she really wants to post it.
function postConfirmedEditForm() { function postConfirmedEditForm() {

View file

@ -78,7 +78,7 @@
<div id="grey" class="grey"></div> <div id="grey" class="grey"></div>
<tal:comment replace="nothing">Popup for confirming an action</tal:comment> <tal:comment replace="nothing">Popup for confirming an action</tal:comment>
<div id="confirmActionPopup" class="popup"> <div id="confirmActionPopup" class="popup">
<form id="confirmActionForm" method="post"> <form id="confirmActionForm" method="post">
<div align="center"> <div align="center">
<p id="appyConfirmText"></p> <p id="appyConfirmText"></p>
@ -96,7 +96,22 @@
tal:attributes="value python:_('no')"/> tal:attributes="value python:_('no')"/>
</div> </div>
</form> </form>
</div> </div>
<tal:comment replace="nothing">Popup for reinitialing the password</tal:comment>
<div id="askPasswordReinitPopup" class="popup" tal:condition="isAnon">
<form id="askPasswordReinitForm" method="post"
tal:attributes="action python: tool.absolute_url() + '/askPasswordReinit'">
<div align="center">
<p tal:content="python: _('app_login')"></p>
<input type="text" size="35" name="login" id="login" value=""/>
<br/><br/>
<input type="button" onClick="doAskPasswordReinit()"
tal:attributes="value python:_('ask_password_reinit')"/>
<input type="button" value="No" onClick="closePopup('askPasswordReinitPopup')"
tal:attributes="value python:_('object_cancel')"/>
</div>
</form>
</div>
</td> </td>
</tr> </tr>
<tal:comment replace="nothing">The user strip</tal:comment> <tal:comment replace="nothing">The user strip</tal:comment>
@ -108,23 +123,28 @@
<tal:comment replace="nothing">The user login form for anonymous users</tal:comment> <tal:comment replace="nothing">The user login form for anonymous users</tal:comment>
<table align="center" tal:condition="python: isAnon and ('/temp_folder/' not in req['ACTUAL_URL'])" <table align="center" tal:condition="python: isAnon and ('/temp_folder/' not in req['ACTUAL_URL'])"
class="login"> class="login">
<tr><td class="userStripText"> <tr>
<form name="loginform" method="post" <td class="userStripText">
<form name="loginform" method="post"
tal:attributes="action python: tool.absolute_url() + '/performLogin'"> tal:attributes="action python: tool.absolute_url() + '/performLogin'">
<input type="hidden" name="js_enabled" id="js_enabled" value="0"/> <input type="hidden" name="js_enabled" id="js_enabled" value="0"/>
<input type="hidden" name="cookies_enabled" id="cookies_enabled" value=""/> <input type="hidden" name="cookies_enabled" id="cookies_enabled" value=""/>
<input type="hidden" name="login_name" id="login_name" value=""/> <input type="hidden" name="login_name" id="login_name" value=""/>
<input type="hidden" name="pwd_empty" id="pwd_empty" value="0"/> <input type="hidden" name="pwd_empty" id="pwd_empty" value="0"/>
<span tal:replace="python: _('app_login')"/>&nbsp; <span tal:replace="python: _('app_login')"/>&nbsp;
<input type="text" size="25" name="__ac_name" id="__ac_name" value=""/>&nbsp; <input type="text" size="25" name="__ac_name" id="__ac_name" value=""/>&nbsp;
<span tal:replace="python: _('app_password')"/>&nbsp; <span tal:replace="python: _('app_password')"/>&nbsp;
<input type="password" size="25" name="__ac_password" id="__ac_password"/> <input type="password" size="25" name="__ac_password" id="__ac_password"/>
<input type="submit" name="submit" onclick="setLoginVars()" <input type="submit" name="submit" onclick="setLoginVars()"
tal:define="label python: _('app_connect')" tal:attributes="value label; alt label;"/> tal:define="label python: _('app_connect')" tal:attributes="value label; alt label;"/>
</form> </form>
</td></tr> </td>
<td class="lostPassword">
<a href="javascript: openPopup('askPasswordReinitPopup')"
tal:content="python: _('forgot_password')"></a></td>
</tr>
</table> </table>
<tal:comment replace="nothing">User info and controls for authenticated users</tal:comment> <tal:comment replace="nothing">User info and controls for authenticated users</tal:comment>
<table tal:condition="not: isAnon" class="buttons" width="99%"> <table tal:condition="not: isAnon" class="buttons" width="99%">