diff --git a/organize/README.txt b/organize/README.txt index 31761d7..d085f2a 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -224,6 +224,26 @@ Now we can also retrieve it from the authentication utility: u'Tom Sawyer' +Change Password +--------------- + + >>> data = {'oldPassword': u'tiger', + ... 'password': u'lion', + ... 'passwordConfirm': u'lion'} + + >>> request = TestRequest() + +We need a principal for testing the login stuff: + + >>> from zope.app.authentication.principalfolder import InternalPrincipal + >>> principal = InternalPrincipal('scott', 'tiger', 'Scotty') + >>> request.setPrincipal(principal) + + >>> from loops.organize.browser import PasswordChange + >>> pwcView = PasswordChange(menu, request, testing=True) + >>> pwcView.changePassword(data) + + Fin de partie ============= diff --git a/organize/browser.py b/organize/browser.py index e8de0a3..8c3d45b 100644 --- a/organize/browser.py +++ b/organize/browser.py @@ -25,6 +25,7 @@ $Id$ from zope import interface, component from zope.app import zapi +from zope.app.authentication.principalfolder import InternalPrincipal from zope.app.form.browser.textwidgets import PasswordWidget as BasePasswordWidget from zope.app.form.interfaces import WidgetInputError from zope.app.pagetemplate import ViewPageTemplateFile @@ -39,8 +40,9 @@ from loops.browser.concept import ConceptView from loops.browser.node import NodeView from loops.browser.concept import ConceptRelationView from loops.organize.interfaces import ANNOTATION_KEY, IMemberRegistrationManager -from loops.organize.interfaces import IMemberRegistration +from loops.organize.interfaces import IMemberRegistration, IPasswordChange from loops.organize.party import getPersonForUser +from loops.organize.util import getInternalPrincipal import loops.browser.util _ = MessageFactory('zope') @@ -73,9 +75,26 @@ class PasswordWidget(BasePasswordWidget): return value +class OldPasswordWidget(BasePasswordWidget): + + def getInputValue(self): + value = super(OldPasswordWidget, self).getInputValue() + if value: + principal = self.request.principal + if not isinstance(principal, InternalPrincipal): + principal = getInternalPrincipal(principal.id) + if not principal.checkPassword(value): + v = _(u'Your old password was not entered correctly.') + self._error = WidgetInputError( + self.context.__name__, self.label, v) + raise self._error + return value + + class MemberRegistration(NodeView, Form): form_fields = FormFields(IMemberRegistration).omit('age') + form_fields['password'].custom_widget = PasswordWidget template = loops.browser.util.dataform label = _(u'Member Registration') @@ -92,10 +111,11 @@ class MemberRegistration(NodeView, Form): def item(self): return self - def xupdate(self): + def update(self): # see cybertools.browser.view.GenericView.update() NodeView.update(self) Form.update(self) + return True @action(_(u'Register')) def handle_register_action(self, action, data): @@ -117,3 +137,53 @@ class MemberRegistration(NodeView, Form): % (self.url, login, message)) return person + +class PasswordChange(NodeView, Form): + + form_fields = FormFields(IPasswordChange).select( + 'oldPassword', 'password', 'passwordConfirm') + form_fields['oldPassword'].custom_widget = OldPasswordWidget + form_fields['password'].custom_widget = PasswordWidget + template = loops.browser.util.dataform + label = _(u'Change Password') + + def __init__(self, context, request, testing=False): + super(PasswordChange, self).__init__(context, request) + if not testing: + self.setUpWidgets() + + @Lazy + def macro(self): + return self.template.macros['content'] + + @Lazy + def item(self): + return self + + def update(self): + # see cybertools.browser.view.GenericView.update() + NodeView.update(self) + Form.update(self) + return True + + @action(_(u'Change Password')) + def handle_change_password_action(self, action, data): + self.changePassword(data) + + def changePassword(self, data=None): + form = data or self.request.form + oldPw = form.get('oldPassword') + pw = form.get('password') + if form.get('passwordConfirm') != pw: + raise ValueError(u'Password and password confirmation do not match.') + regMan = IMemberRegistrationManager(self.context.getLoopsRoot()) + principal = self.request.principal + result = regMan.changePassword(principal, oldPw, pw) + if not result: + raise ValueError(u'Your old password was not entered correctly.') + message = _(u'Your password has been changed') + self.request.response.redirect('%s?message=%s' + % (self.url, message)) + #self.request.response.redirect('%s/logout.html?message=%s' + # % (self.url, message)) + diff --git a/organize/configure.zcml b/organize/configure.zcml index 91331a2..b4054e9 100644 --- a/organize/configure.zcml +++ b/organize/configure.zcml @@ -84,13 +84,12 @@ permission="zope.Public" /> - + diff --git a/organize/member.py b/organize/member.py index 785bb71..60835cf 100644 --- a/organize/member.py +++ b/organize/member.py @@ -22,15 +22,15 @@ Member registration adapter(s). $Id$ """ -from zope.app import zapi from zope import interface, component, schema +from zope.app.component import queryNextUtility from zope.component import adapts from zope.interface import implements from zope.app.authentication.interfaces import IPluggableAuthentication from zope.app.authentication.interfaces import IAuthenticatorPlugin from zope.app.authentication.principalfolder import InternalPrincipal from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent -from zope.app.security.interfaces import IAuthentication +from zope.app.security.interfaces import IAuthentication, PrincipalLookupError from zope.event import notify from zope.i18nmessageid import MessageFactory from zope.cachedescriptors.property import Lazy @@ -39,9 +39,8 @@ from cybertools.typology.interfaces import IType from loops.interfaces import ILoops from loops.concept import Concept from loops.organize.interfaces import IMemberRegistrationManager -from loops.organize.util import getPrincipalFolder, authPluginId - -_ = MessageFactory('zope') +from loops.organize.util import getPrincipalFolder, authPluginId, getInternalPrincipal +from loops.util import _ class MemberRegistrationManager(object): @@ -56,7 +55,6 @@ class MemberRegistrationManager(object): # step 1: create an internal principal in the loops principal folder: pFolder = getPrincipalFolder(self.context) title = firstName and ' '.join((firstName, lastName)) or lastName - # TODO: care for password encryption: principal = InternalPrincipal(userId, password, title) pFolder[userId] = principal # step 2: create a corresponding person concept: @@ -80,6 +78,11 @@ class MemberRegistrationManager(object): notify(ObjectModifiedEvent(person)) return personAdapter - def changePassword(self, oldPw, newPw): - pass + def changePassword(self, principal, oldPw, newPw): + if not isinstance(principal, InternalPrincipal): + principal = getInternalPrincipal(principal.id) + if not principal.checkPassword(oldPw): + return False + principal.setPassword(newPw) + return True diff --git a/organize/util.py b/organize/util.py index 70a90cc..6bf8fc4 100644 --- a/organize/util.py +++ b/organize/util.py @@ -22,7 +22,6 @@ Utilities for the loops.organize package. $Id$ """ -from zope.app import zapi from zope import interface, component, schema from zope.app.authentication.interfaces import IPluggableAuthentication from zope.app.authentication.interfaces import IAuthenticatorPlugin @@ -32,16 +31,37 @@ authPluginId = 'loops' def getPrincipalFolder(context=None): - pau = zapi.getUtility(IAuthentication, context=context) - if not IPluggableAuthentication.providedBy(pau): - raise ValueError(u'There is no pluggable authentication ' - 'utility available.') - if not authPluginId in pau.authenticatorPlugins: - raise ValueError(u'There is no loops authenticator ' - 'plugin available.') - #return component.queryUtility(IAuthenticatorPlugin, authPluginId, - # context=pau) - for name, plugin in pau.getAuthenticatorPlugins(): - if name == authPluginId: - return plugin + pau = component.getUtility(IAuthentication, context=context) + if not IPluggableAuthentication.providedBy(pau): + raise ValueError(u'There is no pluggable authentication ' + 'utility available.') + if not authPluginId in pau.authenticatorPlugins: + raise ValueError(u'There is no loops authenticator ' + 'plugin available.') + for name, plugin in pau.getAuthenticatorPlugins(): + if name == authPluginId: + return plugin + +def getInternalPrincipal(id, context=None): + pau = component.getUtility(IAuthentication, context=context) + if not IPluggableAuthentication.providedBy(pau): + raise ValueError(u'There is no pluggable authentication ' + 'utility available.') + if not id.startswith(pau.prefix): + next = queryNextUtility(pau, IAuthentication) + if next is None: + raise PrincipalLookupError(id) + return next.getPrincipal(id) + id = id[len(pau.prefix):] + for name, authplugin in pau.getAuthenticatorPlugins(): + if not id.startswith(authplugin.prefix): + continue + principal = authplugin.get(id[len(authplugin.prefix):]) + if principal is None: + continue + return principal + next = queryNextUtility(pau, IAuthentication) + if next is not None: + return next.getPrincipal(pau.prefix + id) + raise PrincipalLookupError(id)