Add SMTPAccount with pluggable handlers

darcs-hash:20070523205437-86b55-a1f69f149e57ba943d7163337bcdc8fdecafae40.gz
This commit is contained in:
David Rousselie
2007-05-23 22:54:37 +02:00
parent c13ad7ec93
commit 7446d401a5
7 changed files with 365 additions and 17 deletions

View File

@@ -44,7 +44,7 @@ def suite():
if __name__ == '__main__':
logger = logging.getLogger()
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.INFO)
logger.setLevel(logging.CRITICAL)
coverage.erase()
coverage.start()

View File

@@ -1,4 +1,4 @@
# -*- coding: UTF-8 -*-
# -*- coding: utf-8 -*-
##
## component.py
## Login : David Rousselie <dax@happycoders.org>
@@ -22,13 +22,18 @@
##
import logging
import re
import sys
from sqlobject import *
from pyxmpp.message import Message
from jcl.model.account import PresenceAccount
from jcl.model.account import Account, PresenceAccount
from jcl.jabber.component import Handler, DefaultSubscribeHandler, DefaultUnsubscribeHandler, DefaultPresenceHandler
from jcl.jabber.feeder import FeederComponent, Feeder, MessageSender, HeadlineSender
from jmc.model.account import MailAccount, IMAPAccount, POP3Account
from jmc.model.account import MailAccount, IMAPAccount, POP3Account, SMTPAccount
from jmc.lang import Lang
class MailComponent(FeederComponent):
@@ -53,7 +58,16 @@ class MailComponent(FeederComponent):
lang = lang)
self.feeder = MailFeeder(self)
self.sender = MailSender(self)
self.account_manager.account_classes = (IMAPAccount, POP3Account)
self.account_manager.account_classes = (IMAPAccount, POP3Account, SMTPAccount)
def authenticated(self):
"""Register message handlers"""
FeederComponent.authenticated(self)
self.msg_handlers += [SendMailMessageHandler(), RootSendMailMessageHandler()]
self.subscribe_handlers += [MailSubscribeHandler()]
self.unsubscribe_handlers += [MailUnsubscribeHandler()]
self.available_handlers += [DefaultPresenceHandler()]
self.unavailable_handlers += [DefaultPresenceHandler()]
class MailFeeder(Feeder):
"""Email check"""
@@ -159,4 +173,80 @@ class MailSender(MessageSender, HeadlineSender):
MessageSender.send(self, to_account, subject, body)
elif to_account.action == MailAccount.DIGEST:
HeadlineSender.send(self, to_account, subject, body)
class MailHandler(Handler):
"""Define filter for email address in JID"""
def __init__(self):
Handler.__init__(self)
self.dest_jid_regexp = re.compile(".*%.*")
def filter(self, stanza):
"""Return empty array if JID match '.*%.*@componentJID'"""
if self.dest_jid_regexp.match(stanza.get_to().node):
bare_from_jid = unicode(stanza.get_from().bare())
accounts = Account.select(Account.q.user_jid == bare_from_jid)
if accounts.count() == 0:
raise Exception()
else:
default_account = accounts.newClause(SMTPAccount.q.default_account == True)
if default_account.count() > 0:
return default_account
else:
return accounts
return None
class SendMailMessageHandler(MailHandler):
def __init__(self):
MailHandler.__init__(self)
self.__logger = logging.getLogger("jmc.jabber.component.SendMailMessageHandler")
def handle(self, message, lang, accounts):
to_email = replace(message.get_to().node, '%', '@', 1)
accounts[0].send_email(to_email, message.get_subject(), message.get_body())
class RootSendMailMessageHandler(SendMailMessageHandler):
def __init__(self):
SendMailMessageHandler.__init__(self)
self.__logger = logging.getLogger("jmc.jabber.component.RootSendMailMessageHandler")
def filter(self, message):
name = message.get_to().node
bare_from_jid = unicode(message.get_from().bare())
accounts = Account.select(\
AND(Account.q.name == name, \
Account.q.user_jid == bare_from_jid))
if accounts.count() != 1:
self.__logger.error("Account " + name + " for user " + bare_from_jid + " must be uniq")
return accounts
def handle(self, message, lang, accounts):
# TODO : parse "headers", or advanced addressing
to_email = ""
accounts[0].send_email(to_email, message.get_subject(), message.get_body())
class MailSubscribeHandler(DefaultSubscribeHandler, MailHandler):
"""Use DefaultSubscribeHandler handle method and MailHandler filter"""
def __init__(self):
DefaultSubscribeHandler.__init__(self)
MailHandler.__init__(self)
def filter(self, stanza):
return MailHandler.filter(self, stanza)
def handle(self, stanza, lang, accounts):
return DefaultSubscribeHandler.handle(self, stanza, lang, accounts)
class MailUnsubscribeHandler(DefaultUnsubscribeHandler, MailHandler):
"""Use DefaultUnsubscribeHandler handle method and MailHandler filter"""
def __init__(self):
DefaultUnsubscribeHandler.__init__(self)
MailHandler.__init__(self)
def filter(self, stanza):
return MailHandler.filter(self, stanza)
def handle(self, stanza, lang, accounts):
return DefaultUnsubscribeHandler.handle(self, stanza, lang, accounts)

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
##
## test_component.py
## Login : <dax@happycoders.org>
@@ -27,17 +28,21 @@ import sys
from sqlobject import *
from sqlobject.dbconnection import TheURIOpener
from pyxmpp.presence import Presence
from pyxmpp.message import Message
from jcl.model import account
from jcl.model.account import Account, PresenceAccount
from jcl.jabber.tests.component import DefaultSubscribeHandler_TestCase, DefaultUnsubscribeHandler_TestCase
from jmc.model.account import MailAccount, IMAPAccount, POP3Account
from jmc.jabber.component import MailComponent
from jmc.model.account import MailAccount, IMAPAccount, POP3Account, SMTPAccount
from jmc.jabber.component import MailComponent, SendMailMessageHandler, RootSendMailMessageHandler, MailHandler, MailSubscribeHandler, MailUnsubscribeHandler
if sys.platform == "win32":
DB_PATH = "/c|/temp/test.db"
DB_PATH = "/c|/temp/jmc_test.db"
else:
DB_PATH = "/tmp/test.db"
DB_PATH = "/tmp/jmc_test.db"
DB_URL = DB_PATH# + "?debug=1&debugThreading=1"
class MockStream(object):
@@ -148,6 +153,7 @@ class MailComponent_TestCase(unittest.TestCase):
MailAccount.createTable(ifNotExists = True)
IMAPAccount.createTable(ifNotExists = True)
POP3Account.createTable(ifNotExists = True)
SMTPAccount.createTable(ifNotExists = True)
MockIMAPAccount.createTable(ifNotExists = True)
MockPOP3Account.createTable(ifNotExists = True)
del account.hub.threadConnection
@@ -156,6 +162,7 @@ class MailComponent_TestCase(unittest.TestCase):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
MockPOP3Account.dropTable(ifExists = True)
MockIMAPAccount.dropTable(ifExists = True)
SMTPAccount.dropTable(ifExists = True)
POP3Account.dropTable(ifExists = True)
IMAPAccount.dropTable(ifExists = True)
MailAccount.dropTable(ifExists = True)
@@ -502,9 +509,196 @@ class MailComponent_TestCase(unittest.TestCase):
self.assertTrue(account11.has_connected)
self.assertTrue(account11.marked_all_as_read)
del account.hub.threadConnection
class SendMailMessageHandler_TestCase(unittest.TestCase):
def setUp(self):
self.handler = SendMailMessageHandler()
def test_handle(self):
# TODO
pass
class RootSendMailMessageHandler_TestCase(unittest.TestCase):
def setUp(self):
self.handler = RootSendMailMessageHandler()
if os.path.exists(DB_PATH):
os.unlink(DB_PATH)
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
Account.createTable(ifNotExists = True)
SMTPAccount.createTable(ifNotExists = True)
del account.hub.threadConnection
def tearDown(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
SMTPAccount.dropTable(ifExists = True)
Account.dropTable(ifExists = True)
del TheURIOpener.cachedURIs['sqlite://' + DB_URL]
account.hub.threadConnection.close()
del account.hub.threadConnection
if os.path.exists(DB_PATH):
os.unlink(DB_PATH)
def test_filter(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account11 = SMTPAccount(user_jid = "user1@test.com", \
name = "account11", \
jid = "account11@jcl.test.com")
account12 = SMTPAccount(user_jid = "user1@test.com", \
name = "account12", \
jid = "account12@jcl.test.com")
message = Message(from_jid = "user1@test.com", \
to_jid = "account11@jcl.test.com", \
stanza_type = "normal", \
body = "message")
accounts = self.handler.filter(message)
self.assertEquals(accounts.count(), 1)
del account.hub.threadConnection
def test_filter_wrong_dest(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account11 = SMTPAccount(user_jid = "user1@test.com", \
name = "account11", \
jid = "account11@jcl.test.com")
account12 = SMTPAccount(user_jid = "user1@test.com", \
name = "account12", \
jid = "account12@jcl.test.com")
message = Message(from_jid = "user1@test.com", \
to_jid = "user2%test.com@jcl.test.com", \
stanza_type = "normal", \
body = "message")
accounts = self.handler.filter(message)
self.assertEquals(accounts.count(), 0)
del account.hub.threadConnection
def test_filter_wrong_user(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account11 = SMTPAccount(user_jid = "user1@test.com", \
name = "account11", \
jid = "account11@jcl.test.com")
account12 = SMTPAccount(user_jid = "user1@test.com", \
name = "account12", \
jid = "account12@jcl.test.com")
message = Message(from_jid = "user2@test.com", \
to_jid = "account11@jcl.test.com", \
stanza_type = "normal", \
body = "message")
accounts = self.handler.filter(message)
self.assertEquals(accounts.count(), 0)
del account.hub.threadConnection
class MailHandler_TestCase(unittest.TestCase):
def setUp(self):
self.handler = MailHandler()
if os.path.exists(DB_PATH):
os.unlink(DB_PATH)
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
Account.createTable(ifNotExists = True)
SMTPAccount.createTable(ifNotExists = True)
del account.hub.threadConnection
def tearDown(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
SMTPAccount.dropTable(ifExists = True)
Account.dropTable(ifExists = True)
del TheURIOpener.cachedURIs['sqlite://' + DB_URL]
account.hub.threadConnection.close()
del account.hub.threadConnection
if os.path.exists(DB_PATH):
os.unlink(DB_PATH)
def test_filter(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account11 = SMTPAccount(user_jid = "user1@test.com", \
name = "account11", \
jid = "account11@jcl.test.com")
account11.default_account = True
account12 = SMTPAccount(user_jid = "user1@test.com", \
name = "account12", \
jid = "account12@jcl.test.com")
message = Message(from_jid = "user1@test.com", \
to_jid = "user2%test.com@jcl.test.com", \
stanza_type = "normal", \
body = "message")
accounts = self.handler.filter(message)
self.assertNotEquals(accounts, None)
self.assertEquals(accounts.count(), 1)
self.assertEquals(accounts[0].name, "account11")
del account.hub.threadConnection
def test_filter_no_default(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account11 = SMTPAccount(user_jid = "user1@test.com", \
name = "account11", \
jid = "account11@jcl.test.com")
account12 = SMTPAccount(user_jid = "user1@test.com", \
name = "account12", \
jid = "account12@jcl.test.com")
message = Message(from_jid = "user1@test.com", \
to_jid = "user2%test.com@jcl.test.com", \
stanza_type = "normal", \
body = "message")
accounts = self.handler.filter(message)
self.assertNotEquals(accounts, None)
self.assertEquals(accounts.count(), 2)
self.assertEquals(accounts[0].name, "account11")
del account.hub.threadConnection
def test_filter_wrong_dest(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account11 = SMTPAccount(user_jid = "user1@test.com", \
name = "account11", \
jid = "account11@jcl.test.com")
account12 = SMTPAccount(user_jid = "user1@test.com", \
name = "account12", \
jid = "account12@jcl.test.com")
message = Message(from_jid = "user1@test.com", \
to_jid = "user2test.com@jcl.test.com", \
stanza_type = "normal", \
body = "message")
accounts = self.handler.filter(message)
self.assertEquals(accounts, None)
del account.hub.threadConnection
def test_filter_wrong_account(self):
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
account11 = SMTPAccount(user_jid = "user1@test.com", \
name = "account11", \
jid = "account11@jcl.test.com")
account12 = SMTPAccount(user_jid = "user1@test.com", \
name = "account12", \
jid = "account12@jcl.test.com")
message = Message(from_jid = "user3@test.com", \
to_jid = "user2%test.com@jcl.test.com", \
stanza_type = "normal", \
body = "message")
try:
accounts = self.handler.filter(message)
except Exception, e:
self.assertNotEquals(e, None)
return
finally:
del account.hub.threadConnection
self.fail("No exception catched")
class MailSubscribeHandler_TestCase(DefaultSubscribeHandler_TestCase, MailHandler_TestCase):
def setUp(self):
MailHandler_TestCase.setUp(self)
self.handler = MailSubscribeHandler()
class MailUnsubscribeHandler_TestCase(DefaultUnsubscribeHandler_TestCase, MailHandler_TestCase):
def setUp(self):
MailHandler_TestCase.setUp(self)
self.handler = MailUnsubscribeHandler()
def suite():
return unittest.makeSuite(MailComponent_TestCase, 'test')
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(MailComponent_TestCase, 'test'))
suite.addTest(unittest.makeSuite(SendMailMessageHandler_TestCase, 'test'))
suite.addTest(unittest.makeSuite(RootSendMailMessageHandler_TestCase, 'test'))
suite.addTest(unittest.makeSuite(MailHandler_TestCase, 'test'))
suite.addTest(unittest.makeSuite(MailUnsubscribeHandler_TestCase, 'test'))
suite.addTest(unittest.makeSuite(MailSubscribeHandler_TestCase, 'test'))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='suite')

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
##
## account.py
## Login : <dax@happycoders.org>
@@ -158,7 +159,7 @@ class MailAccount(PresenceAccount):
self.__logger = logging.getLogger("jmc.model.account.MailAccount")
self.connection = None
self.connected = False
self.default_lang_class = Lang.en # TODO: use String
self.default_lang_class = Lang.en
def _get_register_fields(cls, real_class = None):
"""See Account._get_register_fields
@@ -338,7 +339,7 @@ class MailAccount(PresenceAccount):
raise NotImplementedError
class IMAPAccount(MailAccount):
mailbox = StringCol(default = "INBOX") # TODO : set default INBOX in reg_form (use get_register_fields last field ?)
mailbox = StringCol(default = "INBOX")
def _get_register_fields(cls, real_class = None):
"""See Account._get_register_fields
@@ -515,3 +516,59 @@ class POP3Account(MailAccount):
self.get_mail_list()
self.lastmail = self.nb_mail
class SMTPAccount(Account):
"""Send email account"""
login = StringCol(default = "")
password = StringCol(default = None)
host = StringCol(default = "localhost")
port = IntCol(default = 110)
ssl = BoolCol(default = False)
store_password = BoolCol(default = True)
waiting_password_reply = BoolCol(default = False)
default_from = StringCol(default = "nobody@localhost")
default_account = BoolCol(default = False)
def _init(self, *args, **kw):
"""SMTPAccount init
Initialize class attributes"""
Account._init(self, *args, **kw)
self.__logger = logging.getLogger("jmc.model.account.SMTPAccount")
def _get_register_fields(cls, real_class = None):
"""See Account._get_register_fields
"""
def password_post_func(password, default_func):
if password is None or password == "":
return None
return password
if real_class is None:
real_class = cls
return Account.get_register_fields(real_class) + \
[("login", "text-single", None, \
lambda field_value, default_func: account.mandatory_field(field_value), \
lambda : ""), \
("password", "text-private", None, password_post_func, \
lambda : ""), \
("host", "text-single", None, \
lambda field_value, default_func: account.mandatory_field(field_value), \
lambda : ""), \
("port", "text-single", None, \
account.int_post_func, \
lambda : real_class.get_default_port()), \
("ssl", "boolean", None, \
account.default_post_func, \
lambda : False), \
("default_from", "text-single", None, \
lambda field_value, default_func: account.mandatory_field(field_value), \
lambda : ""), \
("store_password", "boolean", None, \
account.default_post_func, \
lambda : True)]
get_register_fields = classmethod(_get_register_fields)
def send_email(self, to_email, subject, body):
pass

View File

@@ -31,9 +31,9 @@ from sqlobject.dbconnection import TheURIOpener
from jcl.model import account
from jcl.model.account import Account, PresenceAccount
from jmc.model.account import MailAccount, POP3Account, IMAPAccount
from jmc.model.account import MailAccount, POP3Account, IMAPAccount, SMTPAccount
from jcl.model.tests.account import PresenceAccount_TestCase
from jcl.model.tests.account import Account_TestCase, PresenceAccount_TestCase
from jmc.model.tests import email_generator, server
if sys.platform == "win32":
@@ -388,12 +388,17 @@ class IMAPAccount_TestCase(unittest.TestCase):
register_fields = IMAPAccount.get_register_fields()
self.assertEquals(len(register_fields), 16)
class SMTPAccount_TestCase(Account_TestCase):
def test_get_register_fields(self):
register_fields = SMTPAccount.get_register_fields()
self.assertEquals(len(register_fields), 7)
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(MailAccount_TestCase, 'test'))
suite.addTest(unittest.makeSuite(POP3Account_TestCase, 'test'))
suite.addTest(unittest.makeSuite(IMAPAccount_TestCase, 'test'))
suite.addTest(unittest.makeSuite(SMTPAccount_TestCase, 'test'))
return suite
if __name__ == '__main__':

View File

@@ -22,7 +22,7 @@
from jcl.runner import JCLRunner
from jmc.model.account import MailAccount, IMAPAccount, POP3Account
from jmc.model.account import MailAccount, IMAPAccount, POP3Account, SMTPAccount
from jmc.jabber.component import MailComponent
from jmc.lang import Lang
@@ -48,6 +48,7 @@ class JMCRunner(JCLRunner):
MailAccount.createTable(ifNotExists = True)
IMAPAccount.createTable(ifNotExists = True)
POP3Account.createTable(ifNotExists = True)
SMTPAccount.createTable(ifNotExists = True)
def run(self):
def run_func():

View File

@@ -33,7 +33,7 @@ from jcl.model.account import Account, PresenceAccount
import jmc
from jmc.runner import JMCRunner
from jmc.model.account import MailAccount, IMAPAccount, POP3Account
from jmc.model.account import MailAccount, IMAPAccount, POP3Account, SMTPAccount
if sys.platform == "win32":
DB_PATH = "/c|/temp/test.db"
@@ -136,6 +136,7 @@ class JMCRunner_TestCase(JCLRunner_TestCase):
MailAccount.dropTable()
IMAPAccount.dropTable()
POP3Account.dropTable()
SMTPAccount.dropTable()
del account.hub.threadConnection
os.unlink(DB_PATH)
self.assertFalse(os.access("/tmp/jmc.pid", os.F_OK))