def test_delete_tenant(self): tenant_name = "tenant1" members = ["*****@*****.**"] by = "*****@*****.**" db.create_tenant(tenant_name, "hoge", self.config) config = { "admins": {"hoge"}, "charset": "iso-2022-jp", "ml_name_format": "ml1-%06d", "new_ml_account": "ml1-new", "days_to_close": 7, "days_to_orphan": 7, "welcome_msg": "welcome_msg", "readme_msg": "readme_msg", "add_msg": "add_msg", "remove_msg": "remove_msg", "reopen_msg": "reopen_msg", "goodbye_msg": "goodbye_msg", "report_subject": "report_subject", "report_msg": "report_msg", "orphaned_subject": "orphaned_subject", "orphaned_msg": "orphaned_msg", "closed_subject": "closed_subject", "closed_msg": "closed_msg", } db.create_ml(tenant_name, "ml1", "hoge", members, by) config['new_ml_account'] = 'ml2-new' db.create_ml(tenant_name, "ml2", "hoge", members, by) ret = db.find_mls({"tenant_name": tenant_name}) self.assertEqual(len(list(ret)), 2) db.delete_tenant(tenant_name) ret = db.find_mls({"tenant_name": tenant_name}) self.assertEqual(len(list(ret)), 0)
def test_add_and_del_members(self): ml_name = ML_NAME % db.increase_counter(self.tenant_name) db.create_ml(self.tenant_name, ml_name, "hoge", set(), "xyz") self.assertEqual(db.get_members(ml_name), set()) db.add_members(ml_name, {"abc", "def"}, "xyz") self.assertEqual(db.get_members(ml_name), {"abc", "def"}) db.add_members(ml_name, {"abc", "ghi"}, "xyz") self.assertEqual(db.get_members(ml_name), {"abc", "def", "ghi"}) db.del_members(ml_name, {"abc", "ghi"}, "xyz") self.assertEqual(db.get_members(ml_name), {"def"}) db.del_members(ml_name, {"abc", "def"}, "xyz") self.assertEqual(db.get_members(ml_name), set()) logs = [ { 'op': const.OP_CREATE, 'by': "xyz", 'members': set(), }, { 'op': const.OP_ADD_MEMBERS, 'by': "xyz", 'members': {"abc", "def"}, }, { 'op': const.OP_ADD_MEMBERS, 'by': "xyz", 'members': {"abc", "ghi"}, }, { 'op': const.OP_DEL_MEMBERS, 'by': "xyz", 'members': {"abc", "ghi"}, }, { 'op': const.OP_DEL_MEMBERS, 'by': "xyz", 'members': {"abc", "def"}, }, ] self.assertEqual(db.get_logs(ml_name), logs)
def test_change_ml_status(self): ml_name = ML_NAME % db.increase_counter(self.tenant_name) db.create_ml(self.tenant_name, ml_name, "hoge", set(), "xyz") self.assertEqual(db.get_members(ml_name), set()) db.change_ml_status(ml_name, const.STATUS_ORPHANED, "xxx") ml = db.get_ml(ml_name) self.assertEqual(ml['status'], const.STATUS_ORPHANED) self.assertEqual(ml['logs'][-1]['op'], const.OP_ORPHAN) db.change_ml_status(ml_name, const.STATUS_CLOSED, "xxx") ml = db.get_ml(ml_name) self.assertEqual(ml['status'], const.STATUS_CLOSED) self.assertEqual(ml['logs'][-1]['op'], const.OP_CLOSE) db.change_ml_status(ml_name, const.STATUS_OPEN, "xxx") ml = db.get_ml(ml_name) self.assertEqual(ml['status'], const.STATUS_OPEN) self.assertEqual(ml['logs'][-1]['op'], const.OP_REOPEN)
def test_create_ml(self): members = ["abc", "def", "ghi"] by = "xyz" for i in range(1, 4): ml_name = ML_NAME % db.increase_counter(self.tenant_name) db.create_ml(self.tenant_name, ml_name, "hoge", members, by) ml = db.get_ml(ml_name) self.assertEqual(ml['ml_name'], ml_name) self.assertEqual(ml['subject'], "hoge") self.assertEqual(ml['members'], members) self.assertEqual(ml['by'], by) self.assertEqual(ml['status'], const.STATUS_NEW) logs = [{ 'op': const.OP_CREATE, 'by': by, 'members': members, }] self.assertEqual(ml['logs'], logs)
def test_mark_mls_orphaned_and_closed(self): ml1_name = ML_NAME % db.increase_counter(self.tenant_name) db.create_ml(self.tenant_name, ml1_name, "hoge", [], "xyz") ml2_name = ML_NAME % db.increase_counter(self.tenant_name) db.create_ml(self.tenant_name, ml2_name, "hoge", [], "xyz") db.change_ml_status(ml2_name, const.STATUS_OPEN, "XYZ") time.sleep(1) now = datetime.now() time.sleep(1) ml3_name = ML_NAME % db.increase_counter(self.tenant_name) db.create_ml(self.tenant_name, ml3_name, "hoge", [], "xyz") db.change_ml_status(ml3_name, const.STATUS_OPEN, "XYZ") db.mark_mls_orphaned(now, "XYZ") ml1 = db.get_ml(ml1_name) ml2 = db.get_ml(ml2_name) ml3 = db.get_ml(ml3_name) self.assertEqual(ml1['status'], const.STATUS_NEW) self.assertEqual(ml2['status'], const.STATUS_ORPHANED) self.assertEqual(ml2['by'], "XYZ") self.assertEqual(ml3['status'], const.STATUS_OPEN) time.sleep(1) db.mark_mls_closed(datetime.now(), "XYZ") ml1 = db.get_ml(ml1_name) ml2 = db.get_ml(ml2_name) ml3 = db.get_ml(ml3_name) self.assertEqual(ml1['status'], const.STATUS_NEW) self.assertEqual(ml2['status'], const.STATUS_CLOSED) self.assertEqual(ml3['status'], const.STATUS_OPEN) self.assertEqual(ml2['by'], "XYZ") logs = [ { 'op': const.OP_CREATE, 'by': "xyz", 'members': [], }, { 'op': const.OP_REOPEN, 'by': "XYZ", }, { 'op': const.OP_ORPHAN, 'by': "XYZ", }, { 'op': const.OP_CLOSE, 'by': "XYZ", }, ] self.assertEqual(ml2['logs'], logs)
def test_find_mls(self): db.create_ml(self.tenant_name, "a", "hoge1", set(), "xyz") time.sleep(1) db.create_ml(self.tenant_name, "b", "hoge2", set(), "xyz") time.sleep(1) db.create_ml(self.tenant_name, "c", "hoge1", set(), "xyz") ret = db.find_mls({"subject": "hoge1"}) self.assertEqual(len(list(ret)), 2) ret = db.find_mls({"subject": "hoge1"}, sortkey='created') self.assertEqual([_['ml_name'] for _ in ret], ["a", "c"]) ret = db.find_mls({}, sortkey='created', reverse=True) self.assertEqual([_['ml_name'] for _ in ret], ["c", "b", "a"])
def process_message(self, peer, mailfrom, rcpttos, data): message = email.message_from_string(data) from_str = message.get('From', "").strip() to_str = message.get('To', "").strip() cc_str = message.get('Cc', "").strip() subject = message.get('Subject', "").strip() try: subject = str(make_header(decode_header(subject))) except: pass command = subject.strip().lower() logging.info("Processing: from=%s|to=%s|cc=%s|subject=%s|", from_str, to_str, cc_str, subject) _from = normalize([from_str]) to = normalize(to_str.split(',')) cc = normalize(cc_str.split(',')) # Quick hack mailfrom = list(_from)[0] # Check cross-post mls = [_ for _ in (to | cc) if _.endswith(self.at_domain)] if len(mls) == 0: logging.error("No ML specified") return const.SMTP_STATUS_NO_ML_SPECIFIED elif len(mls) > 1: logging.error("Can't cross-post a message") return const.SMTP_STATUS_CANT_CROSS_POST # Aquire the ML name ml_address = mls[0] ml_name = ml_address.replace(self.at_domain, "") params = dict(ml_name=ml_name, ml_address=ml_address, mailfrom=mailfrom) # Remove the ML name from to'd and cc'd address lists if ml_address in to: to.remove(ml_address) if ml_address in cc: cc.remove(ml_address) # Is an error mail? if ml_name.endswith(ERROR_SUFFIX): ml_name = ml_name.replace(ERROR_SUFFIX, "") error_str = REMOVE_RFC822.sub( "", message.get('Original-Recipient', "")) error = normalize(error_str.split(',')) if len(error) > 0 and len(ml_name) > 0: logging.error("not delivered to %s for %s", error, ml_name) return # Aquire current tenants tenants = db.find_tenants({"status": const.TENANT_STATUS_ENABLED}) # Want a new ML? for config in tenants: if ml_name == config['new_ml_account']: tenant_name = config['tenant_name'] ml_name = config['ml_name_format'] % \ db.increase_counter(config['tenant_name']) members = (to | cc | _from) - config['admins'] db.create_ml(tenant_name, ml_name, subject, members, mailfrom) ml_address = ml_name + self.at_domain params = dict(ml_name=ml_name, ml_address=ml_address, mailfrom=mailfrom, members=members) message = ensure_multipart(message, config['charset']) self.send_message(config, ml_name, message, mailfrom, params, config['welcome_msg'], 'Welcome.txt') return # Post a message to an existing ML # Check ML exists ml = db.get_ml(ml_name) if ml is None: logging.error("No such ML: %s", ml_name) return const.SMTP_STATUS_NO_SUCH_ML # Set config variable for config in tenants: if ml['tenant_name'] == config['new_ml_account']: break else: if config is None: logging.error("No such tenant: %s", ml['tenant_name']) return const.SMTP_STATUS_NO_SUCH_TENANT message = ensure_multipart(message, config['charset']) # Checking whether the sender is one of the ML members members = db.get_members(ml_name) if mailfrom not in (members | config['admins']): logging.error("Non-member post") return const.SMTP_STATUS_NOT_MEMBER # Update parameters new_ml_address = config['new_ml_account'] + self.at_domain params = dict(ml_name=ml_name, ml_address=ml_address, mailfrom=mailfrom, new_ml_address=new_ml_address, members=members) # Check ML status ml_status = ml['status'] if ml_status == const.STATUS_CLOSED: if command == "reopen": self.send_message(config, ml_name, message, mailfrom, params, config['reopen_msg'], 'Reopen.txt') db.change_ml_status(ml_name, const.STATUS_OPEN, mailfrom) logging.info("reopened %s by %s", ml_name, mailfrom) return logging.error("ML is closed: %s", ml_name) return const.SMTP_STATUS_CLOSED_ML elif command == "close": self.send_message(config, ml_name, message, mailfrom, params, config['goodbye_msg'], 'Goodbye.txt') db.change_ml_status(ml_name, const.STATUS_CLOSED, mailfrom) logging.info("closed %s by %s", ml_name, mailfrom) return if ml_status != const.STATUS_OPEN: db.change_ml_status(ml_name, const.STATUS_OPEN, mailfrom) # Remove admin members from cc cc -= config['admins'] params["cc"] = cc # Remove cc'd members from the ML members if the subject is empty if command == "": if len(cc) > 0: params['members'] = members - cc self.send_message(config, ml_name, message, mailfrom, params, config['remove_msg'], 'RemoveMembers.txt') db.del_members(ml_name, cc, mailfrom) logging.info("removed %s from %s", cc, ml_name) return # Checking Cc: if len(cc) > 0: db.add_members(ml_name, cc, mailfrom) logging.info("added %s into %s", cc, ml_name) members = db.get_members(ml_name) params['members'] = members self.send_message(config, ml_name, message, mailfrom, params, config['add_msg'], 'AddMembers.txt') return # Attach readme and send the post self.send_message(config, ml_name, message, mailfrom, params, config['readme_msg'], 'Readme.txt')