provide a 'password reset' form to change/set a forgotten password
This commit is contained in:
parent
2f74920692
commit
6dde146189
4 changed files with 109 additions and 4 deletions
|
@ -228,6 +228,23 @@ We need a principal for testing the login stuff:
|
|||
>>> pwcView.update()
|
||||
False
|
||||
|
||||
Reset Password
|
||||
--------------
|
||||
|
||||
Invalidates the user account by generating a new password. A mail ist sent to
|
||||
the email address of the person with a link for re-activating the account
|
||||
and enter a new password.
|
||||
|
||||
>>> data = {'loginName': u'dummy',
|
||||
... 'action': 'update'}
|
||||
|
||||
>>> request = TestRequest(form=data)
|
||||
|
||||
>>> from loops.organize.browser.member import PasswordReset
|
||||
>>> pwrView = PasswordReset(menu, request)
|
||||
>>> pwrView.update()
|
||||
True
|
||||
|
||||
|
||||
Pure Person-based Authentication
|
||||
================================
|
||||
|
|
|
@ -45,6 +45,12 @@
|
|||
class="loops.organize.browser.member.PasswordChange"
|
||||
permission="zope.View" />
|
||||
|
||||
<browser:page
|
||||
for="loops.interfaces.INode"
|
||||
name="reset_password.html"
|
||||
class="loops.organize.browser.member.PasswordReset"
|
||||
permission="zope.View" />
|
||||
|
||||
<zope:adapter
|
||||
name="task.html"
|
||||
for="loops.interfaces.IConcept
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2014 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# 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
|
||||
|
@ -25,6 +25,7 @@ from datetime import datetime
|
|||
from email.MIMEText import MIMEText
|
||||
from zope import interface, component
|
||||
from zope.app.authentication.principalfolder import InternalPrincipal
|
||||
from zope.app.authentication.principalfolder import PrincipalInfo
|
||||
from zope.app.form.browser.textwidgets import PasswordWidget as BasePasswordWidget
|
||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||
from zope.app.principalannotation import annotations
|
||||
|
@ -47,9 +48,11 @@ from loops.browser.node import NodeView
|
|||
from loops.common import adapted
|
||||
from loops.concept import Concept
|
||||
from loops.organize.interfaces import ANNOTATION_KEY, IMemberRegistrationManager
|
||||
from loops.organize.interfaces import IMemberRegistration, IPasswordChange
|
||||
from loops.organize.interfaces import IMemberRegistration, IPasswordEntry
|
||||
from loops.organize.interfaces import IPasswordChange, IPasswordReset
|
||||
from loops.organize.party import getPersonForUser, Person
|
||||
from loops.organize.util import getInternalPrincipal, getPrincipalForUserId
|
||||
from loops.organize.util import getPrincipalFolder
|
||||
import loops.browser.util
|
||||
from loops.util import _
|
||||
|
||||
|
@ -86,7 +89,8 @@ class BaseMemberRegistration(NodeView):
|
|||
template = form_macros
|
||||
|
||||
formErrors = dict(
|
||||
confirm_nomatch=FormError(_(u'Password and password confirmation do not match.')),
|
||||
confirm_nomatch=FormError(_(u'Password and password confirmation '
|
||||
u'do not match.')),
|
||||
duplicate_loginname=FormError(_('Login name already taken.')),
|
||||
)
|
||||
|
||||
|
@ -366,7 +370,8 @@ class PasswordChange(NodeView, Form):
|
|||
message = _(u'Your password has been changed.')
|
||||
|
||||
formErrors = dict(
|
||||
confirm_nomatch=FormError(_(u'Password and password confirmation do not match.')),
|
||||
confirm_nomatch=FormError(_(u'Password and password confirmation '
|
||||
u'do not match.')),
|
||||
wrong_oldpw=FormError(_(u'Your old password was not entered correctly.')),
|
||||
)
|
||||
|
||||
|
@ -422,3 +427,72 @@ class PasswordChange(NodeView, Form):
|
|||
formState.severity = max(formState.severity, fi.severity)
|
||||
return formState
|
||||
|
||||
|
||||
class PasswordReset(PasswordChange):
|
||||
|
||||
interface = IPasswordReset
|
||||
message = _(u'Password Reset: You will receive an email with '
|
||||
u'a link to change your password.')
|
||||
|
||||
formErrors = dict(
|
||||
confirm_notfound=FormError(_(u'Invalid user account.')),
|
||||
)
|
||||
|
||||
label = label_submit = _(u'Reset Password')
|
||||
|
||||
@Lazy
|
||||
def data(self):
|
||||
data = dict(loginName=u'')
|
||||
return data
|
||||
|
||||
def update(self):
|
||||
form = self.request.form
|
||||
if not form.get('action'):
|
||||
return True
|
||||
formState = self.formState = self.validate(form)
|
||||
if formState.severity > 0:
|
||||
return True
|
||||
loginName = form.get('loginName')
|
||||
person = principal = None
|
||||
regMan = IMemberRegistrationManager(self.context.getLoopsRoot())
|
||||
authenticator = regMan.getPrincipalFolderFromOption()
|
||||
if authenticator is not None:
|
||||
userId = authenticator.prefix + loginName
|
||||
principal = getPrincipalForUserId(userId)
|
||||
if principal is not None:
|
||||
person = getPersonForUser(self.context, principal=principal)
|
||||
if person is None:
|
||||
fi = formState.fieldInstances['loginName']
|
||||
fi.setError('confirm_notfound', self.formErrors)
|
||||
formState.severity = max(formState.severity, fi.severity)
|
||||
return True
|
||||
person = adapted(person)
|
||||
pa = self.getPrincipalAnnotation(principal)
|
||||
pa['id'] = generateName()
|
||||
pa['timestamp'] = datetime.utcnow()
|
||||
self.notifyEmail(loginName, person.email, pa['id'])
|
||||
url = '%s?messsage=%s' % (self.url, self.message)
|
||||
self.request.response.redirect(url)
|
||||
return False
|
||||
|
||||
def getPrincipalAnnotation(self, principal):
|
||||
return annotations(principal).get(ANNOTATION_KEY, None)
|
||||
|
||||
def notifyEmail(self, userid, recipient, id):
|
||||
baseUrl = absoluteURL(self.context.getMenu(), self.request)
|
||||
url = u'%s/selfservice_confirmation.html?login=%s&id=%s' % (
|
||||
baseUrl, userid, id,)
|
||||
recipients = [recipient]
|
||||
subject = _(u'password_reset_mail_subject')
|
||||
message = _(u'password_reset_mail_text') + u':\n\n'
|
||||
message = (message + url).encode('UTF-8')
|
||||
senderInfo = self.globalOptions('email.sender')
|
||||
sender = senderInfo and senderInfo[0] or 'info@loops.cy55.de'
|
||||
sender = sender.encode('UTF-8')
|
||||
msg = MIMEText(message, 'plain', 'utf-8')
|
||||
msg['Subject'] = subject.encode('UTF-8')
|
||||
msg['From'] = sender
|
||||
msg['To'] = ', '.join(recipients)
|
||||
mailhost = component.getUtility(IMailDelivery, 'Mail')
|
||||
mailhost.send(sender, recipients, msg.as_string())
|
||||
|
||||
|
|
|
@ -124,6 +124,14 @@ class IPasswordChange(IPasswordEntry):
|
|||
required=True,)
|
||||
|
||||
|
||||
class IPasswordReset(Interface):
|
||||
|
||||
loginName = schema.TextLine(title=_(u'User ID'),
|
||||
description=_(u'Your login name.'),
|
||||
required=True,)
|
||||
loginName.nostore = True
|
||||
|
||||
|
||||
class IMemberRegistration(IBasePerson, IPasswordEntry):
|
||||
""" Schema for registering a new member (user + person).
|
||||
"""
|
||||
|
|
Loading…
Add table
Reference in a new issue