diff options
Diffstat (limited to 'src/mailman')
| -rw-r--r-- | src/mailman/handlers/docs/calc-recips.rst | 35 | ||||
| -rw-r--r-- | src/mailman/handlers/member_recipients.py | 8 | ||||
| -rw-r--r-- | src/mailman/handlers/owner_recipients.py | 47 | ||||
| -rw-r--r-- | src/mailman/handlers/tests/test_recipients.py | 200 | ||||
| -rw-r--r-- | src/mailman/model/docs/requests.rst | 8 | ||||
| -rw-r--r-- | src/mailman/runners/docs/lmtp.rst | 2 | ||||
| -rw-r--r-- | src/mailman/testing/testing.cfg | 3 |
7 files changed, 259 insertions, 44 deletions
diff --git a/src/mailman/handlers/docs/calc-recips.rst b/src/mailman/handlers/docs/calc-recips.rst index 51a836c82..1439e978f 100644 --- a/src/mailman/handlers/docs/calc-recips.rst +++ b/src/mailman/handlers/docs/calc-recips.rst @@ -6,10 +6,10 @@ Every message that makes it through to the list membership gets sent to a set of recipient addresses. These addresses are calculated by one of the handler modules and depends on a host of factors. - >>> mlist = create_list('_xtest@example.com') + >>> mlist = create_list('test@example.com') -Recipients are calculate from the list members, so add a bunch of members to -start out with. First, create a bunch of addresses... +Recipients are calculate from the list membership, so first some people +subscribe to the mailing list... :: >>> from mailman.interfaces.usermanager import IUserManager @@ -35,42 +35,25 @@ start out with. First, create a bunch of addresses... ...then make some of the members digest members. - >>> from mailman.core.constants import DeliveryMode + >>> from mailman.interfaces.member import DeliveryMode >>> member_d.preferences.delivery_mode = DeliveryMode.plaintext_digests >>> member_e.preferences.delivery_mode = DeliveryMode.mime_digests >>> member_f.preferences.delivery_mode = DeliveryMode.summary_digests -Short-circuiting -================ +Regular delivery recipients +=========================== -Sometimes, the list of recipients already exists in the message metadata. -This can happen for example, when a message was previously delivered to some -but not all of the recipients. -:: +Regular delivery recipients are those people who get messages from the list as +soon as they are posted. In other words, these folks are not digest members. >>> msg = message_from_string("""\ ... From: Xavier Person <xperson@example.com> ... ... Something of great import. ... """) - >>> recipients = set(('qperson@example.com', 'zperson@example.com')) - >>> msgdata = dict(recipients=recipients) - - >>> handler = config.handlers['member-recipients'] - >>> handler.process(mlist, msg, msgdata) - >>> dump_list(msgdata['recipients']) - qperson@example.com - zperson@example.com - - -Regular delivery recipients -=========================== - -Regular delivery recipients are those people who get messages from the list as -soon as they are posted. In other words, these folks are not digest members. - >>> msgdata = {} + >>> handler = config.handlers['member-recipients'] >>> handler.process(mlist, msg, msgdata) >>> dump_list(msgdata['recipients']) aperson@example.com diff --git a/src/mailman/handlers/member_recipients.py b/src/mailman/handlers/member_recipients.py index 421d5a1dc..956ea6adc 100644 --- a/src/mailman/handlers/member_recipients.py +++ b/src/mailman/handlers/member_recipients.py @@ -23,13 +23,14 @@ on the `recipients' attribute of the message. This attribute is used by the SendmailDeliver and BulkDeliver modules. """ -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'MemberRecipients', ] + from zope.interface import implements from mailman.config import config @@ -82,10 +83,9 @@ class MemberRecipients: # Bad Urgent: password, so reject it instead of passing it on. # I think it's better that the sender know they screwed up # than to deliver it normally. - listname = mlist.display_name text = _("""\ -Your urgent message to the $listname mailing list was not authorized for -delivery. The original message as received by Mailman is attached. +Your urgent message to the $mlist.display_name mailing list was not authorized +for delivery. The original message as received by Mailman is attached. """) raise errors.RejectMessage(wrap(text)) # Calculate the regular recipients of the message diff --git a/src/mailman/handlers/owner_recipients.py b/src/mailman/handlers/owner_recipients.py index 9e4bbf174..80ed3e598 100644 --- a/src/mailman/handlers/owner_recipients.py +++ b/src/mailman/handlers/owner_recipients.py @@ -17,19 +17,48 @@ """Calculate the list owner recipients (includes moderators).""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ - 'process', + 'OwnerRecipients', ] +from zope.interface import implements + +from mailman.config import config +from mailman.core.i18n import _ +from mailman.interfaces.handler import IHandler +from mailman.interfaces.member import DeliveryStatus + + -def process(mlist, msg, msgdata): - """Add owner recipients.""" - # The recipients are the owner and the moderator - msgdata['recipients'] = mlist.owner + mlist.moderator - # Don't decorate these messages with the header/footers - msgdata['nodecorate'] = True - msgdata['personalize'] = False +class OwnerRecipients: + """Calculate the owner (and moderator) recipients for -owner postings.""" + + implements(IHandler) + + name = 'owner-recipients' + description = _('Calculate the owner and moderator recipients.') + + def process(self, mlist, msg, msgdata): + """See `IHandler`.""" + # Short circuit if we've already calculated the recipients list, + # regardless of whether the list is empty or not. + if 'recipients' in msgdata: + return + # -owner messages go to both the owners and moderators, which is most + # conveniently accessed via the administrators roster. + recipients = set(admin.address.email + for admin in mlist.administrators.members + if admin.delivery_status == DeliveryStatus.enabled) + # To prevent -owner messages from going into a black hole, if there + # are no administrators available, the message goes to the site owner. + if len(recipients) == 0: + msgdata['recipients'] = set((config.mailman.site_owner,)) + else: + msgdata['recipients'] = recipients + # Don't decorate these messages with the header/footers. Eventually + # we should support unique decorations for owner emails. + msgdata['nodecorate'] = True diff --git a/src/mailman/handlers/tests/test_recipients.py b/src/mailman/handlers/tests/test_recipients.py new file mode 100644 index 000000000..29cd5b64a --- /dev/null +++ b/src/mailman/handlers/tests/test_recipients.py @@ -0,0 +1,200 @@ +# Copyright (C) 2012 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""Testing various recipients stuff.""" + +from __future__ import absolute_import, print_function, unicode_literals + +__metaclass__ = type +__all__ = [ + 'TestMemberRecipients', + 'TestOwnerRecipients', + ] + + +import unittest + +from zope.component import getUtility +from mailman.app.lifecycle import create_list +from mailman.config import config +from mailman.interfaces.member import DeliveryMode, DeliveryStatus, MemberRole +from mailman.interfaces.usermanager import IUserManager +from mailman.testing.helpers import specialized_message_from_string as mfs +from mailman.testing.layers import ConfigLayer + + + +class TestMemberRecipients(unittest.TestCase): + """Test regular member recipient calculation.""" + + layer = ConfigLayer + + def setUp(self): + self._mlist = create_list('test@example.com') + self._manager = getUtility(IUserManager) + anne = self._manager.create_address('anne@example.com') + bart = self._manager.create_address('bart@example.com') + cris = self._manager.create_address('cris@example.com') + dave = self._manager.create_address('dave@example.com') + self._anne = self._mlist.subscribe(anne, MemberRole.member) + self._bart = self._mlist.subscribe(bart, MemberRole.member) + self._cris = self._mlist.subscribe(cris, MemberRole.member) + self._dave = self._mlist.subscribe(dave, MemberRole.member) + self._process = config.handlers['member-recipients'].process + self._msg = mfs("""\ +From: Elle Person <elle@example.com> +To: test@example.com + +""") + + def test_shortcircuit(self): + # When there are already recipients in the message metadata, those are + # used instead of calculating them from the list membership. + recipients = set(('zperson@example.com', 'yperson@example.com')) + msgdata = dict(recipients=recipients) + self._process(self._mlist, self._msg, msgdata) + self.assertEqual(msgdata['recipients'], recipients) + + def test_calculate_recipients(self): + # The normal path just adds the list's regular members. + msgdata = {} + self._process(self._mlist, self._msg, msgdata) + self.assertEqual(msgdata['recipients'], set(('anne@example.com', + 'bart@example.com', + 'cris@example.com', + 'dave@example.com'))) + + def test_digest_members_not_included(self): + # Digest members are not included in the recipients calculated by this + # handler. + self._cris.preferences.delivery_mode = DeliveryMode.mime_digests + msgdata = {} + self._process(self._mlist, self._msg, msgdata) + self.assertEqual(msgdata['recipients'], set(('anne@example.com', + 'bart@example.com', + 'dave@example.com'))) + + + +class TestOwnerRecipients(unittest.TestCase): + """Test owner recipient calculation.""" + + layer = ConfigLayer + + def setUp(self): + self._mlist = create_list('test@example.com') + self._manager = getUtility(IUserManager) + anne = self._manager.create_address('anne@example.com') + bart = self._manager.create_address('bart@example.com') + cris = self._manager.create_address('cris@example.com') + dave = self._manager.create_address('dave@example.com') + # Make Cris and Dave owners of the mailing list. + self._anne = self._mlist.subscribe(anne, MemberRole.member) + self._bart = self._mlist.subscribe(bart, MemberRole.member) + self._cris = self._mlist.subscribe(cris, MemberRole.owner) + self._dave = self._mlist.subscribe(dave, MemberRole.owner) + self._process = config.handlers['owner-recipients'].process + self._msg = mfs("""\ +From: Elle Person <elle@example.com> +To: test-owner@example.com + +""") + + def test_shortcircuit(self): + # When there are already recipients in the message metadata, those are + # used instead of calculating them from the owner membership. + recipients = set(('zperson@example.com', 'yperson@example.com')) + msgdata = dict(recipients=recipients) + self._process(self._mlist, self._msg, msgdata) + self.assertEqual(msgdata['recipients'], recipients) + + def test_calculate_recipients(self): + # The normal path just adds the list's owners. + msgdata = {} + self._process(self._mlist, self._msg, msgdata) + self.assertEqual(msgdata['recipients'], set(('cris@example.com', + 'dave@example.com'))) + + def test_with_moderators(self): + # Moderators are included in the owner recipient list. + elle = self._manager.create_address('elle@example.com') + fred = self._manager.create_address('fred@example.com') + gwen = self._manager.create_address('gwen@example.com') + self._mlist.subscribe(elle, MemberRole.moderator) + self._mlist.subscribe(fred, MemberRole.moderator) + self._mlist.subscribe(gwen, MemberRole.owner) + msgdata = {} + self._process(self._mlist, self._msg, msgdata) + self.assertEqual(msgdata['recipients'], set(('cris@example.com', + 'dave@example.com', + 'elle@example.com', + 'fred@example.com', + 'gwen@example.com'))) + + def test_dont_decorate(self): + # Messages to the administrators don't get decorated. + msgdata = {} + self._process(self._mlist, self._msg, msgdata) + self.assertTrue(msgdata['nodecorate']) + + def test_omit_disabled_owners(self): + # Owner memberships can be disabled, and these folks will not get the + # messages. + self._dave.preferences.delivery_status = DeliveryStatus.by_user + msgdata = {} + self._process(self._mlist, self._msg, msgdata) + self.assertEqual(msgdata['recipients'], set(('cris@example.com',))) + + def test_include_membership_disabled_owner_enabled(self): + # If an address is subscribed to a mailing list as both an owner and a + # member, and their membership is disabled but their ownership + # subscription is not, they still get owner email. + dave = self._manager.get_address('dave@example.com') + member = self._mlist.subscribe(dave, MemberRole.member) + member.preferences.delivery_status = DeliveryStatus.by_user + msgdata = {} + self._process(self._mlist, self._msg, msgdata) + self.assertEqual(msgdata['recipients'], set(('cris@example.com', + 'dave@example.com'))) + # Dave disables his owner membership but re-enables his list + # membership. He will not get the owner emails now. + member.preferences.delivery_status = DeliveryStatus.enabled + self._dave.preferences.delivery_status = DeliveryStatus.by_user + msgdata = {} + self._process(self._mlist, self._msg, msgdata) + self.assertEqual(msgdata['recipients'], set(('cris@example.com',))) + + def test_all_owners_disabled(self): + # If all the owners are disabled, then the site owner gets the + # message. This prevents a list's -owner address from going into a + # black hole. + self._cris.preferences.delivery_status = DeliveryStatus.by_user + self._dave.preferences.delivery_status = DeliveryStatus.by_user + msgdata = {} + self._process(self._mlist, self._msg, msgdata) + self.assertEqual(msgdata['recipients'], set(('noreply@example.com',))) + + def test_no_owners(self): + # If a list has no owners or moderators, then the site owner gets the + # message. This prevents a list's -owner address from going into a + # black hole. + self._cris.unsubscribe() + self._dave.unsubscribe() + self.assertEqual(self._mlist.administrators.member_count, 0) + msgdata = {} + self._process(self._mlist, self._msg, msgdata) + self.assertEqual(msgdata['recipients'], set(('noreply@example.com',))) diff --git a/src/mailman/model/docs/requests.rst b/src/mailman/model/docs/requests.rst index 1e461e36c..068cc84f6 100644 --- a/src/mailman/model/docs/requests.rst +++ b/src/mailman/model/docs/requests.rst @@ -664,7 +664,7 @@ The admin message is sent to the moderators. Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Subject: A Test List subscription notification - From: changeme@example.com + From: noreply@example.com To: alist-owner@example.com Message-ID: ... Date: ... @@ -676,7 +676,7 @@ The admin message is sent to the moderators. >>> dump_msgdata(messages[0].msgdata) _parsemsg : False - envsender : changeme@example.com + envsender : noreply@example.com listname : alist@example.com nodecorate : True recipients : set([]) @@ -888,7 +888,7 @@ The goodbye message... Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Subject: A Test List unsubscription notification - From: changeme@example.com + From: noreply@example.com To: alist-owner@example.com Message-ID: ... Date: ... @@ -899,7 +899,7 @@ The goodbye message... >>> dump_msgdata(messages[1].msgdata) _parsemsg : False - envsender : changeme@example.com + envsender : noreply@example.com listname : alist@example.com nodecorate : True recipients : set([]) diff --git a/src/mailman/runners/docs/lmtp.rst b/src/mailman/runners/docs/lmtp.rst index 2b6c4b42b..3ce145907 100644 --- a/src/mailman/runners/docs/lmtp.rst +++ b/src/mailman/runners/docs/lmtp.rst @@ -306,7 +306,7 @@ Messages to the `-owner` address also go to the incoming processor. 1 >>> dump_msgdata(messages[0].msgdata) _parsemsg : False - envsender : changeme@example.com + envsender : noreply@example.com listname : mylist@example.com original_size: ... subaddress : owner diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg index d503247de..b7e80ff02 100644 --- a/src/mailman/testing/testing.cfg +++ b/src/mailman/testing/testing.cfg @@ -22,6 +22,9 @@ #class: mailman.database.postgresql.PostgreSQLDatabase #url: postgres://barry:barry@localhost/mailman +[mailman] +site_owner: noreply@example.com + [mta] smtp_port: 9025 lmtp_port: 9024 |
