Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/loops into bbmaster

This commit is contained in:
Helmut Merz 2015-08-29 11:08:21 +02:00
commit 2ae8a60b18
4 changed files with 107 additions and 8 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
*.pyc *.pyc
*.pyo
*.project *.project
*.pydevproject *.pydevproject
*.sublime-project *.sublime-project

View file

@ -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 # 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 # 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 email.MIMEText import MIMEText
from zope import interface, component from zope import interface, component
from zope.app.authentication.principalfolder import InternalPrincipal 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.form.browser.textwidgets import PasswordWidget as BasePasswordWidget
from zope.app.pagetemplate import ViewPageTemplateFile from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.principalannotation import annotations from zope.app.principalannotation import annotations
@ -47,9 +48,11 @@ from loops.browser.node import NodeView
from loops.common import adapted from loops.common import adapted
from loops.concept import Concept from loops.concept import Concept
from loops.organize.interfaces import ANNOTATION_KEY, IMemberRegistrationManager 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.party import getPersonForUser, Person
from loops.organize.util import getInternalPrincipal, getPrincipalForUserId from loops.organize.util import getInternalPrincipal, getPrincipalForUserId
from loops.organize.util import getPrincipalFolder
import loops.browser.util import loops.browser.util
from loops.util import _ from loops.util import _
@ -86,7 +89,8 @@ class BaseMemberRegistration(NodeView):
template = form_macros template = form_macros
formErrors = dict( 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.')), duplicate_loginname=FormError(_('Login name already taken.')),
) )
@ -187,7 +191,9 @@ class MemberRegistration(BaseMemberRegistration, CreateForm):
result = regMan.register(login, pw, result = regMan.register(login, pw,
form.get('lastName'), form.get('firstName'), form.get('lastName'), form.get('firstName'),
email=form.get('email'), email=form.get('email'),
phoneNumbers=[x for x in phoneNumbers if x]) phoneNumbers=[x for x in phoneNumbers if x],
salutation=form.get('salutation'),
academicTitle=form.get('academicTitle'))
if isinstance(result, dict): if isinstance(result, dict):
fi = formState.fieldInstances[result['fieldName']] fi = formState.fieldInstances[result['fieldName']]
fi.setError(result['error'], self.formErrors) fi.setError(result['error'], self.formErrors)
@ -210,6 +216,8 @@ class SecureMemberRegistration(BaseMemberRegistration, CreateForm):
@Lazy @Lazy
def schema(self): def schema(self):
schema = super(SecureMemberRegistration, self).schema schema = super(SecureMemberRegistration, self).schema
schema.fields.remove('salutation')
schema.fields.remove('academicTitle')
schema.fields.remove('birthDate') schema.fields.remove('birthDate')
schema.fields.remove('password') schema.fields.remove('password')
schema.fields.remove('passwordConfirm') schema.fields.remove('passwordConfirm')
@ -366,7 +374,8 @@ class PasswordChange(NodeView, Form):
message = _(u'Your password has been changed.') message = _(u'Your password has been changed.')
formErrors = dict( 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.')), wrong_oldpw=FormError(_(u'Your old password was not entered correctly.')),
) )
@ -422,3 +431,84 @@ class PasswordChange(NodeView, Form):
formState.severity = max(formState.severity, fi.severity) formState.severity = max(formState.severity, fi.severity)
return formState 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())
class FixPersonRoles(object):
def __call__(self):
concepts = self.context['concepts']
for p in concepts['person'].getChildren([concepts['hasType']]):
person = adapted(p)
userId = person.userId
print '***', userId
person.userId = userId
return 'blubb'

View file

@ -186,7 +186,7 @@ class SendEmail(FormController):
subject = form.get('subject') or u'' subject = form.get('subject') or u''
message = form.get('mailbody') or u'' message = form.get('mailbody') or u''
recipients = form.get('recipients') or [] recipients = form.get('recipients') or []
recipients += (form.get('addrRecipients') or u'').split('\n') recipients += (form.get('addrecipients') or u'').split('\n')
# TODO: remove duplicates # TODO: remove duplicates
person = getPersonForUser(self.context, self.request) person = getPersonForUser(self.context, self.request)
sender = person and adapted(person).email or 'loops@unknown.com' sender = person and adapted(person).email or 'loops@unknown.com'

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de # Copyright (c) 2015 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -82,7 +82,7 @@ class LoginName(schema.TextLine):
super(LoginName, self)._validate(userId) super(LoginName, self)._validate(userId)
if userId in getPrincipalFolder(self.context): if userId in getPrincipalFolder(self.context):
raiseValidationError( raiseValidationError(
_(u'There is alread a user with ID $userId.', _(u'There is already a user with ID $userId.',
mapping=dict(userId=userId))) mapping=dict(userId=userId)))
@ -124,6 +124,14 @@ class IPasswordChange(IPasswordEntry):
required=True,) 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): class IMemberRegistration(IBasePerson, IPasswordEntry):
""" Schema for registering a new member (user + person). """ Schema for registering a new member (user + person).
""" """