summaryrefslogtreecommitdiff
path: root/src/mailman
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman')
-rw-r--r--src/mailman/handlers/docs/calc-recips.rst35
-rw-r--r--src/mailman/handlers/member_recipients.py8
-rw-r--r--src/mailman/handlers/owner_recipients.py47
-rw-r--r--src/mailman/handlers/tests/test_recipients.py200
-rw-r--r--src/mailman/model/docs/requests.rst8
-rw-r--r--src/mailman/runners/docs/lmtp.rst2
-rw-r--r--src/mailman/testing/testing.cfg3
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