[gen] SMTP and LDAP configuration updated. Module appy.gen.mail can now be used independently of a gen-application.
This commit is contained in:
parent
4947e2956c
commit
ecc3f07a09
|
@ -1,6 +1,6 @@
|
|||
'''This script allows to check a LDAP connection.'''
|
||||
import sys
|
||||
from appy.shared.ldap_connector import LdapConnector
|
||||
from appy.shared.ldap import LdapConnector
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class LdapTester:
|
||||
|
|
|
@ -418,7 +418,8 @@ class Transition:
|
|||
# (Allowed, State) need to be updated here.
|
||||
if reindex and not obj.isTemporary(): obj.reindex()
|
||||
# Send notifications if needed
|
||||
if doNotify and self.notify and obj.getTool(True).mailEnabled:
|
||||
mail = obj.getTool().getProductConfig(True).mail
|
||||
if doNotify and self.notify and mail and mail.enabled:
|
||||
sendNotification(obj.appy(), self, name, wf)
|
||||
# Return a message to the user if needed
|
||||
if not doSay or (name == '_init_'): return
|
||||
|
|
|
@ -52,74 +52,6 @@ class Tool(Model):
|
|||
class User(Model):
|
||||
'''Subclass me to extend or modify the User class.'''
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class LdapConfig:
|
||||
'''Parameters for authenticating users to an LDAP server.'''
|
||||
ldapAttributes = { 'loginAttribute':None, 'emailAttribute':'email',
|
||||
'fullNameAttribute':'title',
|
||||
'firstNameAttribute':'firstName',
|
||||
'lastNameAttribute':'name' }
|
||||
|
||||
def __init__(self):
|
||||
self.server = '' # Name of the LDAP server
|
||||
self.port = None # Port for this server.
|
||||
# Login and password of the technical power user that the Appy
|
||||
# application will use to connect to the LDAP.
|
||||
self.adminLogin = ''
|
||||
self.adminPassword = ''
|
||||
# LDAP attribute to use as login for authenticating users.
|
||||
self.loginAttribute = 'dn' # Can also be "mail", "sAMAccountName", "cn"
|
||||
# LDAP attributes for storing email
|
||||
self.emailAttribute = None
|
||||
# LDAP attribute for storing full name (first + last name)
|
||||
self.fullNameAttribute = None
|
||||
# Alternately, LDAP attributes for storing 1st & last names separately.
|
||||
self.firstNameAttribute = None
|
||||
self.lastNameAttribute = None
|
||||
# LDAP classes defining the users stored in the LDAP.
|
||||
self.userClasses = ('top', 'person')
|
||||
self.baseDn = '' # Base DN where to find users in the LDAP.
|
||||
self.scope = 'SUBTREE' # Scope of the search within self.baseDn
|
||||
|
||||
def getServerUri(self):
|
||||
'''Returns the complete URI for accessing the LDAP, ie
|
||||
"ldap://some.ldap.server:389".'''
|
||||
port = self.port or 389
|
||||
return 'ldap://%s:%d' % (self.server, port)
|
||||
|
||||
def getUserFilterValues(self, login):
|
||||
'''Gets the filter values required to perform a query for finding user
|
||||
corresponding to p_login in the LDAP.'''
|
||||
res = [(self.loginAttribute, login)]
|
||||
for userClass in self.userClasses:
|
||||
res.append( ('objectClass', userClass) )
|
||||
return res
|
||||
|
||||
def getUserAttributes(self):
|
||||
'''Gets the attributes we want to get from the LDAP for characterizing
|
||||
a user.'''
|
||||
res = []
|
||||
for name in self.ldapAttributes.iterkeys():
|
||||
if getattr(self, name):
|
||||
res.append(getattr(self, name))
|
||||
return res
|
||||
|
||||
def getUserParams(self, ldapData):
|
||||
'''Formats the user-related p_ldapData retrieved from the ldap, as a
|
||||
dict of params usable for creating or updating the corresponding
|
||||
Appy user.'''
|
||||
res = {}
|
||||
for name, appyName in self.ldapAttributes.iteritems():
|
||||
if not appyName: continue
|
||||
# Get the name of the attribute as known in the LDAP.
|
||||
ldapName = getattr(self, name)
|
||||
if not ldapName: continue
|
||||
if ldapData.has_key(ldapName) and ldapData[ldapName]:
|
||||
value = ldapData[ldapName]
|
||||
if isinstance(value, list): value = value[0]
|
||||
res[appyName] = value
|
||||
return res
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class Config:
|
||||
'''If you want to specify some configuration parameters for appy.gen and
|
||||
|
@ -169,8 +101,11 @@ class Config:
|
|||
# Create a group for every global role?
|
||||
groupsForGlobalRoles = False
|
||||
# When using a LDAP for authenticating users, place an instance of class
|
||||
# LdapConfig above in the field below.
|
||||
# appy.shared.ldap.LdapConfig in the field below.
|
||||
ldap = None
|
||||
# When using a SMTP mail server for sending emails from your app, place an
|
||||
# instance of class appy.gen.mail.MailConfig in the field below.
|
||||
mail = None
|
||||
# For an app, the default folder where to look for static content for the
|
||||
# user interface (CSS, Javascript and image files) is folder "ui" within
|
||||
# this app.
|
||||
|
|
|
@ -319,6 +319,20 @@ class ZopeInstaller:
|
|||
import Products
|
||||
install_product(self.app, Products.__path__[1], 'ZCTextIndex', [], {})
|
||||
|
||||
def logConnectedServers(self):
|
||||
'''Simply log the names of servers (LDAP, mail...) this app wants to
|
||||
connnect to.'''
|
||||
cfg = self.config.appConfig
|
||||
servers = []
|
||||
# Are we connected to a LDAP server for authenticating our users?
|
||||
for sv in ('ldap', 'mail'):
|
||||
if not getattr(cfg, sv): continue
|
||||
svConfig = getattr(cfg, sv)
|
||||
enabled = svConfig.enabled and 'enabled' or 'disabled'
|
||||
servers.append('%s (%s, %s)' % (svConfig.server, sv, enabled))
|
||||
if servers:
|
||||
self.logger.info('server(s) %s configured.' % ', '.join(servers))
|
||||
|
||||
def install(self):
|
||||
self.installDependencies()
|
||||
self.patchZope()
|
||||
|
@ -332,6 +346,8 @@ class ZopeInstaller:
|
|||
self.installCatalog()
|
||||
self.installTool()
|
||||
self.installUi()
|
||||
# Log connections to external servers (ldap, mail...)
|
||||
self.logConnectedServers()
|
||||
# Perform migrations if required
|
||||
Migrator(self).run()
|
||||
# Update Appy version in the database
|
||||
|
|
79
gen/mail.py
79
gen/mail.py
|
@ -8,31 +8,62 @@ from email.Header import Header
|
|||
from appy.shared.utils import sequenceTypes
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
def sendMail(tool, to, subject, body, attachments=None):
|
||||
'''Sends a mail, via p_tool.mailHost, to p_to (a single email recipient or
|
||||
a list of recipients). Every (string) recipient can be an email address
|
||||
or a string of the form "[name] <[email]>".
|
||||
class MailConfig:
|
||||
'''Parameters for conneting to a SMTP server.'''
|
||||
def __init__(self, fromName=None, fromEmail='info@appyframework.org',
|
||||
server='localhost', port=25, login=None, password=None,
|
||||
enabled=True):
|
||||
# The name that will appear in the "from" part of the messages
|
||||
self.fromName = fromName
|
||||
# The email that will appear in the "from" part of the messages
|
||||
self.fromEmail = fromEmail
|
||||
# The SMTP server address
|
||||
self.server = server
|
||||
# The SMTP server port
|
||||
self.port = port
|
||||
# Optional credentials to the SMTP server.
|
||||
self.login = login
|
||||
self.password = password
|
||||
# Is this server connection enabled ?
|
||||
self.enabled = enabled
|
||||
|
||||
def getFrom(self):
|
||||
'''Gets the "from" part of the messages to send.'''
|
||||
if self.fromName: return '%s <%s>' % (self.fromName, self.fromEmail)
|
||||
return self.fromEmail
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
def sendMail(config, to, subject, body, attachments=None, log=None):
|
||||
'''Sends a mail, via the smtp server defined in the p_config (an instance of
|
||||
appy.gen.mail.MailConfig above), to p_to (a single email recipient or a
|
||||
list of recipients). Every (string) recipient can be an email address or
|
||||
a string of the form "[name] <[email]>".
|
||||
|
||||
p_attachment must be a list or tuple whose elements can have 2 forms:
|
||||
1. a tuple (fileName, fileContent): "fileName" is the name of the file
|
||||
as a string; "fileContent" is the file content, also as a string;
|
||||
2. a appy.fields.file.FileInfo instance.
|
||||
|
||||
p_log can be a function/method accepting a single string arg.
|
||||
'''
|
||||
if not config:
|
||||
if log: log('Must send mail but no smtp server configured.')
|
||||
return
|
||||
# Just log things if mail is disabled
|
||||
fromAddress = tool.mailFrom
|
||||
if not tool.mailEnabled or not tool.mailHost:
|
||||
if not tool.mailHost:
|
||||
fromAddress = config.getFrom()
|
||||
if not config.enabled or not config.server:
|
||||
if not config.server:
|
||||
msg = ' (no mailhost defined)'
|
||||
else:
|
||||
msg = ''
|
||||
tool.log('mail disabled%s: should send mail from %s to %s.' % \
|
||||
if log:
|
||||
log('mail disabled%s: should send mail from %s to %s.' % \
|
||||
(msg, fromAddress, str(to)))
|
||||
tool.log('subject: %s' % subject)
|
||||
tool.log('body: %s' % body)
|
||||
if attachments:
|
||||
tool.log('%d attachment(s).' % len(attachments))
|
||||
log('subject: %s' % subject)
|
||||
log('body: %s' % body)
|
||||
if attachments and log: log('%d attachment(s).' % len(attachments))
|
||||
return
|
||||
tool.log('sending mail from %s to %s (subject: %s).' % \
|
||||
if log: log('sending mail from %s to %s (subject: %s).' % \
|
||||
(fromAddress, str(to), subject))
|
||||
# Create the base MIME message
|
||||
body = MIMEText(body, 'plain', 'utf-8')
|
||||
|
@ -73,26 +104,18 @@ def sendMail(tool, to, subject, body, attachments=None):
|
|||
msg.attach(part)
|
||||
# Send the email
|
||||
try:
|
||||
smtpInfo = tool.mailHost.split(':', 3)
|
||||
login = password = None
|
||||
if len(smtpInfo) == 2:
|
||||
# We simply have server and port
|
||||
server, port = smtpInfo
|
||||
else:
|
||||
# We also have login and password
|
||||
server, port, login, password = smtpInfo
|
||||
smtpServer = smtplib.SMTP(server, port=int(port))
|
||||
if login:
|
||||
smtpServer.login(login, password)
|
||||
smtpServer = smtplib.SMTP(config.server, port=config.port)
|
||||
if config.login:
|
||||
smtpServer.login(config.login, config.password)
|
||||
res = smtpServer.sendmail(fromAddress, [to], msg.as_string())
|
||||
smtpServer.quit()
|
||||
if res:
|
||||
tool.log('could not send mail to some recipients. %s' % str(res),
|
||||
if res and log:
|
||||
log('could not send mail to some recipients. %s' % str(res),
|
||||
type='warning')
|
||||
except smtplib.SMTPException, e:
|
||||
tool.log('mail sending failed: %s' % str(e), type='error')
|
||||
if log: log('mail sending failed: %s' % str(e), type='error')
|
||||
except socket.error, se:
|
||||
tool.log('mail sending failed: %s' % str(se), type='error')
|
||||
if log: log('mail sending failed: %s' % str(se), type='error')
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
def sendNotification(obj, transition, transitionName, workflow):
|
||||
|
|
|
@ -8,11 +8,10 @@ from appy.gen import utils as gutils
|
|||
from appy.gen.mixins import BaseMixin
|
||||
from appy.gen.wrappers import AbstractWrapper
|
||||
from appy.gen.descriptors import ClassDescriptor
|
||||
from appy.gen.mail import sendMail
|
||||
from appy.shared import mimeTypes
|
||||
from appy.shared import utils as sutils
|
||||
from appy.shared.data import languages
|
||||
from appy.shared.ldap_connector import LdapConnector
|
||||
from appy.shared.ldap import LdapConnector
|
||||
try:
|
||||
from AccessControl.ZopeSecurityPolicy import _noroles
|
||||
except ImportError:
|
||||
|
@ -1334,7 +1333,7 @@ class ToolMixin(BaseMixin):
|
|||
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)
|
||||
appyTool.sendMail(email, subject, body)
|
||||
return self.goto(backUrl, msg)
|
||||
|
||||
def doPasswordReinit(self):
|
||||
|
@ -1363,7 +1362,7 @@ class ToolMixin(BaseMixin):
|
|||
map = {'password': newPassword, 'siteUrl': siteUrl}
|
||||
body = self.translate('new_password_body', mapping=map,
|
||||
format='text')
|
||||
sendMail(appyTool, email, subject, body)
|
||||
appyTool.sendMail(email, subject, body)
|
||||
os.remove(tokenFile)
|
||||
res = self.goto(siteUrl, self.translate('new_password_sent'))
|
||||
if not res:
|
||||
|
|
|
@ -656,7 +656,9 @@ class ToolWrapper(AbstractWrapper):
|
|||
|
||||
def sendMail(self, to, subject, body, attachments=None):
|
||||
'''Sends a mail. See doc for appy.gen.mail.sendMail.'''
|
||||
sendMail(self, to, subject, body, attachments=attachments)
|
||||
mailConfig = self.o.getProductConfig(True).mail
|
||||
sendMail(mailConfig, to, subject, body, attachments=attachments,
|
||||
log=self.log)
|
||||
|
||||
def formatDate(self, date, format=None, withHour=True, language=None):
|
||||
'''Check doc @ToolMixin::formatDate.'''
|
||||
|
|
|
@ -5,6 +5,78 @@ except ImportError:
|
|||
# For people that do not care about ldap.
|
||||
ldap = None
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class LdapConfig:
|
||||
'''Parameters for authenticating users to an LDAP server. This class is
|
||||
used by gen-applications. For a pure, appy-independent LDAP connector,
|
||||
see the class LdapConnector below.'''
|
||||
ldapAttributes = { 'loginAttribute':None, 'emailAttribute':'email',
|
||||
'fullNameAttribute':'title',
|
||||
'firstNameAttribute':'firstName',
|
||||
'lastNameAttribute':'name' }
|
||||
|
||||
def __init__(self):
|
||||
self.server = '' # Name of the LDAP server
|
||||
self.port = None # Port for this server.
|
||||
# Login and password of the technical power user that the Appy
|
||||
# application will use to connect to the LDAP.
|
||||
self.adminLogin = ''
|
||||
self.adminPassword = ''
|
||||
# LDAP attribute to use as login for authenticating users.
|
||||
self.loginAttribute = 'dn' # Can also be "mail", "sAMAccountName", "cn"
|
||||
# LDAP attributes for storing email
|
||||
self.emailAttribute = None
|
||||
# LDAP attribute for storing full name (first + last name)
|
||||
self.fullNameAttribute = None
|
||||
# Alternately, LDAP attributes for storing 1st & last names separately.
|
||||
self.firstNameAttribute = None
|
||||
self.lastNameAttribute = None
|
||||
# LDAP classes defining the users stored in the LDAP.
|
||||
self.userClasses = ('top', 'person')
|
||||
self.baseDn = '' # Base DN where to find users in the LDAP.
|
||||
self.scope = 'SUBTREE' # Scope of the search within self.baseDn
|
||||
# Is this server connection enabled ?
|
||||
self.enabled = True
|
||||
|
||||
def getServerUri(self):
|
||||
'''Returns the complete URI for accessing the LDAP, ie
|
||||
"ldap://some.ldap.server:389".'''
|
||||
port = self.port or 389
|
||||
return 'ldap://%s:%d' % (self.server, port)
|
||||
|
||||
def getUserFilterValues(self, login):
|
||||
'''Gets the filter values required to perform a query for finding user
|
||||
corresponding to p_login in the LDAP.'''
|
||||
res = [(self.loginAttribute, login)]
|
||||
for userClass in self.userClasses:
|
||||
res.append( ('objectClass', userClass) )
|
||||
return res
|
||||
|
||||
def getUserAttributes(self):
|
||||
'''Gets the attributes we want to get from the LDAP for characterizing
|
||||
a user.'''
|
||||
res = []
|
||||
for name in self.ldapAttributes.iterkeys():
|
||||
if getattr(self, name):
|
||||
res.append(getattr(self, name))
|
||||
return res
|
||||
|
||||
def getUserParams(self, ldapData):
|
||||
'''Formats the user-related p_ldapData retrieved from the ldap, as a
|
||||
dict of params usable for creating or updating the corresponding
|
||||
Appy user.'''
|
||||
res = {}
|
||||
for name, appyName in self.ldapAttributes.iteritems():
|
||||
if not appyName: continue
|
||||
# Get the name of the attribute as known in the LDAP.
|
||||
ldapName = getattr(self, name)
|
||||
if not ldapName: continue
|
||||
if ldapData.has_key(ldapName) and ldapData[ldapName]:
|
||||
value = ldapData[ldapName]
|
||||
if isinstance(value, list): value = value[0]
|
||||
res[appyName] = value
|
||||
return res
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class LdapConnector:
|
||||
'''This class manages the communication with a LDAP server.'''
|
Loading…
Reference in a new issue