diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/docs/NEWS.rst | 2 | ||||
| -rw-r--r-- | src/mailman/model/mailinglist.py | 6 | ||||
| -rw-r--r-- | src/mailman/rules/approved.py | 7 | ||||
| -rw-r--r-- | src/mailman/rules/docs/approved.rst | 297 | ||||
| -rw-r--r-- | src/mailman/rules/tests/test_approved.py | 358 |
5 files changed, 449 insertions, 221 deletions
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst index 71a2d0cdc..54f6ec83f 100644 --- a/src/mailman/docs/NEWS.rst +++ b/src/mailman/docs/NEWS.rst @@ -25,6 +25,8 @@ Architecture * The `news` runner and queue has been renamed to the more accurate `nntp`. The runner has also been ported to Mailman 3 (LP: #967409). Beta testers can can safely remove `$var_dir/queue/news`. + * A mailing list's *moderator password* is no longer stored in the clear; it + is hashed with the currently selected scheme. Configuration ------------- diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py index e397d59d6..d51c89514 100644 --- a/src/mailman/model/mailinglist.py +++ b/src/mailman/model/mailinglist.py @@ -29,8 +29,8 @@ import os import string from storm.locals import ( - And, Bool, DateTime, Float, Int, Pickle, Reference, Store, TimeDelta, - Unicode) + And, Bool, DateTime, Float, Int, Pickle, RawStr, Reference, Store, + TimeDelta, Unicode) from urlparse import urljoin from zope.component import getUtility from zope.interface import implements @@ -160,7 +160,7 @@ class MailingList(Model): max_num_recipients = Int() member_moderation_notice = Unicode() mime_is_default_digest = Bool() - moderator_password = Unicode() + moderator_password = RawStr() new_member_options = Int() news_moderation = Enum(NewsModeration) news_prefix_subject_too = Bool() diff --git a/src/mailman/rules/approved.py b/src/mailman/rules/approved.py index 927a96ee5..3e2b7bc83 100644 --- a/src/mailman/rules/approved.py +++ b/src/mailman/rules/approved.py @@ -17,7 +17,7 @@ """Look for moderator pre-approval.""" -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ @@ -26,7 +26,9 @@ __all__ = [ import re + from email.iterators import typed_subpart_iterator +from flufl.password import verify from zope.interface import implements from mailman.core.i18n import _ @@ -117,7 +119,8 @@ class Approved: else: for header in HEADERS: del msg[header] - return password is not missing and password == mlist.moderator_password + return (password is not missing and + verify(mlist.moderator_password, password)) diff --git a/src/mailman/rules/docs/approved.rst b/src/mailman/rules/docs/approved.rst index 3e1206563..9c61a7419 100644 --- a/src/mailman/rules/docs/approved.rst +++ b/src/mailman/rules/docs/approved.rst @@ -2,20 +2,27 @@ Pre-approved postings ===================== -Messages can contain a pre-approval, which is used to bypass the message -approval queue. This has several use cases: +Messages can contain a pre-approval, which is used to bypass the normal +message approval queue. This has several use cases: -- A list administrator can send an emergency message to the mailing list from - an unregistered address, say if they are away from their normal email. + - A list administrator can send an emergency message to the mailing list + from an unregistered address, for example if they are away from their + normal email. -- An automated script can be programmed to send a message to an otherwise - moderated list. + - An automated script can be programmed to send a message to an otherwise + moderated list. In order to support this, a mailing list can be given a *moderator password* which is shared among all the administrators. - >>> mlist = create_list('_xtest@example.com') - >>> mlist.moderator_password = 'abcxyz' + >>> mlist = create_list('test@example.com') + +This password will not be stored in clear text, so it must be hashed using the +configured hash protocol. + + >>> from flufl.password import lookup, make_secret + >>> scheme = lookup(config.passwords.password_scheme.upper()) + >>> mlist.moderator_password = make_secret('super secret', scheme) The ``approved`` rule determines whether the message contains the proper approval or not. @@ -28,8 +35,8 @@ approval or not. No approval =========== -If the message has no ``Approve:`` or ``Approved:`` header (or their ``X-`` -equivalents), then the rule does not match. +The preferred header to check for approval is ``Approved:``. If the message +does not have this header, the rule will not match. >>> msg = message_from_string("""\ ... From: aperson@example.com @@ -39,123 +46,95 @@ equivalents), then the rule does not match. >>> rule.check(mlist, msg, {}) False -If the message has an ``Approve:``, ``Approved:``, ``X-Approve:``, or -``X-Approved:`` header with a value that does not match the moderator -password, then the rule does not match. However, the header is still removed. -:: +If the rule has an ``Approved`` header, but the value of this header does not +match the moderator password, the rule will not match. Note that the header +must contain the clear text version of the password. - >>> msg['Approve'] = '12345' + >>> msg['Approved'] = 'not the password' >>> rule.check(mlist, msg, {}) False - >>> print msg['approve'] - None - >>> del msg['approve'] - >>> msg['Approved'] = '12345' - >>> rule.check(mlist, msg, {}) - False - >>> print msg['approved'] - None - >>> del msg['approved'] - >>> msg['X-Approve'] = '12345' - >>> rule.check(mlist, msg, {}) - False - >>> print msg['x-approve'] - None +The message is approved +======================= - >>> del msg['x-approve'] - >>> msg['X-Approved'] = '12345' - >>> rule.check(mlist, msg, {}) - False - >>> print msg['x-approved'] - None +By adding an ``Approved`` header with a matching password, the rule will +match. - >>> del msg['x-approved'] + >>> del msg['approved'] + >>> msg['Approved'] = 'super secret' + >>> rule.check(mlist, msg, {}) + True -Using an approval header -======================== +Alternative headers +=================== -If the moderator password is given in an ``Approve:`` header, then the rule -matches, and the ``Approve:`` header is stripped. +Other headers can be used to stash the moderator password. This rule also +checks the ``Approve`` header. - >>> msg['Approve'] = 'abcxyz' + >>> del msg['approved'] + >>> msg['Approve'] = 'super secret' >>> rule.check(mlist, msg, {}) True - >>> print msg['approve'] - None -Similarly, for the ``Approved:`` header. +Similarly, an ``X-Approved`` header can be used. >>> del msg['approve'] - >>> msg['Approved'] = 'abcxyz' + >>> msg['X-Approved'] = 'super secret' >>> rule.check(mlist, msg, {}) True - >>> print msg['approved'] - None -The headers ``X-Approve:`` and ``X-Approved:`` are treated the same way. -:: +And finally, an ``X-Approve`` header can be used. - >>> del msg['approved'] - >>> msg['X-Approve'] = 'abcxyz' + >>> del msg['x-approved'] + >>> msg['X-Approve'] = 'super secret' >>> rule.check(mlist, msg, {}) True - >>> print msg['x-approve'] - None - >>> del msg['x-approve'] - >>> msg['X-Approved'] = 'abcxyz' + +Removal of header +================= + +Technically, rules should not have side-effects, however this rule does remove +the ``Approved`` header (LP: #973790) when it matches. + + >>> del msg['x-approved'] + >>> msg['Approved'] = 'super secret' >>> rule.check(mlist, msg, {}) True - >>> print msg['x-approved'] + >>> print msg['approved'] None - >>> del msg['x-approved'] +It also removes the header when it doesn't match. If the rule didn't do this, +then the mailing list could be probed for its moderator password. + + >>> msg['Approved'] = 'not the password' + >>> rule.check(mlist, msg, {}) + False + >>> print msg['approved'] + None Using a pseudo-header ===================== -Different mail user agents have varying degrees to which they support custom -headers like ``Approve:`` and ``Approved:``. For this reason, Mailman also -supports using a *pseudo-header*, which is really just the first -non-whitespace line in the payload of the message. If this pseudo-header -looks like a matching ``Approve:`` or ``Approved:`` header, the message is -similarly allowed to pass. +Mail programs have varying degrees to which they support custom headers like +``Approved:``. For this reason, Mailman also supports using a +*pseudo-header*, which is really just the first non-whitespace line in the +payload of the message. If this pseudo-header looks like a matching +``Approved:`` header, the message is similarly allowed to pass. >>> msg = message_from_string("""\ ... From: aperson@example.com ... - ... Approve: abcxyz + ... Approved: super secret ... An important message. ... """) >>> rule.check(mlist, msg, {}) True -The pseudo-header is removed. - - >>> print msg.as_string() - From: aperson@example.com - Content-Transfer-Encoding: 7bit - MIME-Version: 1.0 - Content-Type: text/plain; charset="us-ascii" - <BLANKLINE> - An important message. - <BLANKLINE> - -Similarly for the ``Approved:`` header. -:: - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... Approved: abcxyz - ... An important message. - ... """) - >>> rule.check(mlist, msg, {}) - True +The pseudo-header is always removed from the body of plain text messages. >>> print msg.as_string() From: aperson@example.com @@ -173,28 +152,7 @@ the pseudo-header line is still removed. >>> msg = message_from_string("""\ ... From: aperson@example.com ... - ... Approve: 123456 - ... An important message. - ... """) - >>> rule.check(mlist, msg, {}) - False - - >>> print msg.as_string() - From: aperson@example.com - Content-Transfer-Encoding: 7bit - MIME-Version: 1.0 - Content-Type: text/plain; charset="us-ascii" - <BLANKLINE> - An important message. - <BLANKLINE> - -Similarly for the ``Approved:`` header. -:: - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... Approved: 123456 + ... Approved: not the password ... An important message. ... """) >>> rule.check(mlist, msg, {}) @@ -225,13 +183,13 @@ be used with MIME documents. ... --AAA ... Content-Type: application/x-ignore ... - ... Approve: 123456 + ... Approved: not the password ... The above line will be ignored. ... ... --AAA ... Content-Type: text/plain ... - ... Approve: abcxyz + ... Approved: super secret ... An important message. ... --AAA-- ... """) @@ -248,52 +206,7 @@ Like before, the pseudo-header is removed, but only from the text parts. --AAA Content-Type: application/x-ignore <BLANKLINE> - Approve: 123456 - The above line will be ignored. - <BLANKLINE> - --AAA - Content-Transfer-Encoding: 7bit - MIME-Version: 1.0 - Content-Type: text/plain; charset="us-ascii" - <BLANKLINE> - An important message. - --AAA-- - <BLANKLINE> - -The same goes for the ``Approved:`` message. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... MIME-Version: 1.0 - ... Content-Type: multipart/mixed; boundary="AAA" - ... - ... --AAA - ... Content-Type: application/x-ignore - ... - ... Approved: 123456 - ... The above line will be ignored. - ... - ... --AAA - ... Content-Type: text/plain - ... - ... Approved: abcxyz - ... An important message. - ... --AAA-- - ... """) - >>> rule.check(mlist, msg, {}) - True - -And the header is removed. - - >>> print msg.as_string() - From: aperson@example.com - MIME-Version: 1.0 - Content-Type: multipart/mixed; boundary="AAA" - <BLANKLINE> - --AAA - Content-Type: application/x-ignore - <BLANKLINE> - Approved: 123456 + Approved: not the password The above line will be ignored. <BLANKLINE> --AAA @@ -305,51 +218,7 @@ And the header is removed. --AAA-- <BLANKLINE> -Here, the correct password is in the non-``text/plain`` part, so it is ignored. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... MIME-Version: 1.0 - ... Content-Type: multipart/mixed; boundary="AAA" - ... - ... --AAA - ... Content-Type: application/x-ignore - ... - ... Approve: abcxyz - ... The above line will be ignored. - ... - ... --AAA - ... Content-Type: text/plain - ... - ... Approve: 123456 - ... An important message. - ... --AAA-- - ... """) - >>> rule.check(mlist, msg, {}) - False - -And yet the pseudo-header is still stripped. - - >>> print msg.as_string() - From: aperson@example.com - MIME-Version: 1.0 - Content-Type: multipart/mixed; boundary="AAA" - <BLANKLINE> - --AAA - Content-Type: application/x-ignore - <BLANKLINE> - Approve: abcxyz - The above line will be ignored. - <BLANKLINE> - --AAA - Content-Transfer-Encoding: 7bit - MIME-Version: 1.0 - Content-Type: text/plain; charset="us-ascii" - <BLANKLINE> - An important message. - --AAA-- - -As before, the same goes for the ``Approved:`` header. +If the correct password is in the non-``text/plain`` part, it is ignored. >>> msg = message_from_string("""\ ... From: aperson@example.com @@ -359,20 +228,20 @@ As before, the same goes for the ``Approved:`` header. ... --AAA ... Content-Type: application/x-ignore ... - ... Approved: abcxyz + ... Approved: super secret ... The above line will be ignored. ... ... --AAA ... Content-Type: text/plain ... - ... Approved: 123456 + ... Approved: not the password ... An important message. ... --AAA-- ... """) >>> rule.check(mlist, msg, {}) False -And the pseudo-header is removed. +Pseudo-header is still stripped, but only from the ``text/plain`` part. >>> print msg.as_string() From: aperson@example.com @@ -382,7 +251,7 @@ And the pseudo-header is removed. --AAA Content-Type: application/x-ignore <BLANKLINE> - Approved: abcxyz + Approved: super secret The above line will be ignored. <BLANKLINE> --AAA @@ -397,10 +266,9 @@ And the pseudo-header is removed. Stripping text/html parts ========================= -Because some mail readers will include both a ``text/plain`` part and a -``text/html`` alternative, the ``approved`` rule has to search the -alternatives and strip anything that looks like an ``Approve:`` or -``Approved:`` headers. +Because some mail programs will include both a ``text/plain`` part and a +``text/html`` alternative, the rule must search the alternatives and strip +anything that looks like an ``Approved:`` header. >>> msg = message_from_string("""\ ... From: aperson@example.com @@ -413,7 +281,7 @@ alternatives and strip anything that looks like an ``Approve:`` or ... <html> ... <head></head> ... <body> - ... <b>Approved: abcxyz</b> + ... <b>Approved: super secret</b> ... <p>The above line will be ignored. ... </body> ... </html> @@ -421,7 +289,7 @@ alternatives and strip anything that looks like an ``Approve:`` or ... --AAA ... Content-Type: text/plain ... - ... Approved: abcxyz + ... Approved: super secret ... An important message. ... --AAA-- ... """) @@ -457,7 +325,8 @@ And the header-like text in the ``text/html`` part was stripped. --AAA-- <BLANKLINE> -This is true even if the rule does not match. +This is true even if the rule does not match (i.e. the incorrect password was +given). :: >>> msg = message_from_string("""\ @@ -471,7 +340,7 @@ This is true even if the rule does not match. ... <html> ... <head></head> ... <body> - ... <b>Approve: 123456</b> + ... <b>Approved: not the password</b> ... <p>The above line will be ignored. ... </body> ... </html> @@ -479,7 +348,7 @@ This is true even if the rule does not match. ... --AAA ... Content-Type: text/plain ... - ... Approve: 123456 + ... Approved: not the password ... An important message. ... --AAA-- ... """) diff --git a/src/mailman/rules/tests/test_approved.py b/src/mailman/rules/tests/test_approved.py index 8ffe68aa9..d078556ba 100644 --- a/src/mailman/rules/tests/test_approved.py +++ b/src/mailman/rules/tests/test_approved.py @@ -15,19 +15,25 @@ # You should have received a copy of the GNU General Public License along with # GNU Mailman. If not, see <http://www.gnu.org/licenses/>. -"""Test the mime_delete handler.""" +"""Test the `approved` handler.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'TestApproved', + 'TestApprovedNonASCII', + 'TestApprovedPseudoHeader', + 'TestApprovedPseudoHeaderMIME', ] import unittest +from flufl.password import lookup, make_secret + from mailman.app.lifecycle import create_list +from mailman.config import config from mailman.rules import approved from mailman.testing.helpers import ( specialized_message_from_string as mfs) @@ -42,6 +48,355 @@ class TestApproved(unittest.TestCase): def setUp(self): self._mlist = create_list('test@example.com') + scheme = lookup(config.passwords.password_scheme.upper()) + self._mlist.moderator_password = make_secret('super secret', scheme) + self._rule = approved.Approved() + self._msg = mfs("""\ +From: anne@example.com +To: test@example.com +Subject: A Message with non-ascii body +Message-ID: <ant> +MIME-Version: 1.0 + +A message body. +""") + + def test_approved_header(self): + self._msg['Approved'] = 'super secret' + result = self._rule.check(self._mlist, self._msg, {}) + self.assertTrue(result) + + def test_approve_header(self): + self._msg['Approve'] = 'super secret' + result = self._rule.check(self._mlist, self._msg, {}) + self.assertTrue(result) + + def test_x_approved_header(self): + self._msg['X-Approved'] = 'super secret' + result = self._rule.check(self._mlist, self._msg, {}) + self.assertTrue(result) + + def test_x_approve_header(self): + self._msg['X-Approve'] = 'super secret' + result = self._rule.check(self._mlist, self._msg, {}) + self.assertTrue(result) + + def test_approved_header_wrong_password(self): + self._msg['Approved'] = 'not the password' + result = self._rule.check(self._mlist, self._msg, {}) + self.assertFalse(result) + + def test_approve_header_wrong_password(self): + self._msg['Approve'] = 'not the password' + result = self._rule.check(self._mlist, self._msg, {}) + self.assertFalse(result) + + def test_x_approved_header_wrong_password(self): + self._msg['X-Approved'] = 'not the password' + result = self._rule.check(self._mlist, self._msg, {}) + self.assertFalse(result) + + def test_x_approve_header_wrong_password(self): + self._msg['X-Approve'] = 'not the password' + result = self._rule.check(self._mlist, self._msg, {}) + self.assertFalse(result) + + def test_removes_approved_header(self): + self._msg['Approved'] = 'super secret' + self._rule.check(self._mlist, self._msg, {}) + self.assertEqual(self._msg['approved'], None) + + def test_removes_approve_header(self): + self._msg['Approve'] = 'super secret' + self._rule.check(self._mlist, self._msg, {}) + self.assertEqual(self._msg['approve'], None) + + def test_removes_x_approved_header(self): + self._msg['X-Approved'] = 'super secret' + self._rule.check(self._mlist, self._msg, {}) + self.assertEqual(self._msg['x-approved'], None) + + def test_removes_x_approve_header(self): + self._msg['X-Approve'] = 'super secret' + self._rule.check(self._mlist, self._msg, {}) + self.assertEqual(self._msg['x-approve'], None) + + def test_removes_approved_header_wrong_password(self): + self._msg['Approved'] = 'not the password' + self._rule.check(self._mlist, self._msg, {}) + self.assertEqual(self._msg['approved'], None) + + def test_removes_approve_header_wrong_password(self): + self._msg['Approve'] = 'not the password' + self._rule.check(self._mlist, self._msg, {}) + self.assertEqual(self._msg['approve'], None) + + def test_removes_x_approved_header_wrong_password(self): + self._msg['X-Approved'] = 'not the password' + self._rule.check(self._mlist, self._msg, {}) + self.assertEqual(self._msg['x-approved'], None) + + def test_removes_x_approve_header_wrong_password(self): + self._msg['X-Approve'] = 'not the password' + self._rule.check(self._mlist, self._msg, {}) + self.assertEqual(self._msg['x-approve'], None) + + + +class TestApprovedPseudoHeader(unittest.TestCase): + """Test the approved handler.""" + + layer = ConfigLayer + + def setUp(self): + self._mlist = create_list('test@example.com') + scheme = lookup(config.passwords.password_scheme.upper()) + self._mlist.moderator_password = make_secret('super secret', scheme) + self._rule = approved.Approved() + self._msg = mfs("""\ +From: anne@example.com +To: test@example.com +Subject: A Message with non-ascii body +Message-ID: <ant> +MIME-Version: 1.0 + +""") + + def test_approved_pseudo_header(self): + self._msg.set_payload("""\ +Approved: super secret + """) + result = self._rule.check(self._mlist, self._msg, {}) + self.assertTrue(result) + + def test_approve_pseudo_header(self): + self._msg.set_payload("""\ +Approve: super secret + """) + result = self._rule.check(self._mlist, self._msg, {}) + self.assertTrue(result) + + def test_x_approved_pseudo_header(self): + self._msg.set_payload("""\ +X-Approved: super secret + """) + result = self._rule.check(self._mlist, self._msg, {}) + self.assertTrue(result) + + def test_x_approve_pseudo_header(self): + self._msg.set_payload("""\ +X-Approve: super secret + """) + result = self._rule.check(self._mlist, self._msg, {}) + self.assertTrue(result) + + def test_approved_pseudo_header_wrong_password(self): + self._msg.set_payload("""\ +Approved: not the password + """) + result = self._rule.check(self._mlist, self._msg, {}) + self.assertFalse(result) + + def test_approve_pseudo_header_wrong_password(self): + self._msg.set_payload("""\ +Approve: not the password + """) + result = self._rule.check(self._mlist, self._msg, {}) + self.assertFalse(result) + + def test_x_approved_pseudo_header_wrong_password(self): + self._msg.set_payload("""\ +X-Approved: not the password + """) + result = self._rule.check(self._mlist, self._msg, {}) + self.assertFalse(result) + + def test_x_approve_pseudo_header_wrong_password(self): + self._msg.set_payload("""\ +X-Approve: not the password + """) + result = self._rule.check(self._mlist, self._msg, {}) + self.assertFalse(result) + + def test_removes_approved_pseudo_header(self): + self._msg.set_payload("""\ +Approved: super secret + """) + self._rule.check(self._mlist, self._msg, {}) + self.assertFalse('Approved' in self._msg.get_payload()) + + def test_removes_approve_pseudo_header(self): + self._msg.set_payload("""\ +Approve: super secret + """) + self._rule.check(self._mlist, self._msg, {}) + self.assertFalse('Approve' in self._msg.get_payload()) + + def test_removes_x_approved_pseudo_header(self): + self._msg.set_payload("""\ +X-Approved: super secret + """) + self._rule.check(self._mlist, self._msg, {}) + self.assertFalse('X-Approved' in self._msg.get_payload()) + + def test_removes_x_approve_pseudo_header(self): + self._msg.set_payload("""\ +X-Approve: super secret + """) + self._rule.check(self._mlist, self._msg, {}) + self.assertFalse('X-Approve' in self._msg.get_payload()) + + def test_removes_approved_pseudo_header_wrong_password(self): + self._msg.set_payload("""\ +Approved: not the password + """) + self._rule.check(self._mlist, self._msg, {}) + self.assertFalse('Approved' in self._msg.get_payload()) + + def test_removes_approve_pseudo_header_wrong_password(self): + self._msg.set_payload("""\ +Approve: not the password + """) + self._rule.check(self._mlist, self._msg, {}) + self.assertFalse('Approve' in self._msg.get_payload()) + + def test_removes_x_approved_pseudo_header_wrong_password(self): + self._msg.set_payload("""\ +X-Approved: not the password + """) + self._rule.check(self._mlist, self._msg, {}) + self.assertFalse('X-Approved' in self._msg.get_payload()) + + def test_removes_x_approve_pseudo_header_wrong_password(self): + self._msg.set_payload("""\ +X-Approve: not the password + """) + self._rule.check(self._mlist, self._msg, {}) + self.assertFalse('X-Approve' in self._msg.get_payload()) + + + +class TestApprovedPseudoHeaderMIME(unittest.TestCase): + """Test the approved handler.""" + + layer = ConfigLayer + + def setUp(self): + self._mlist = create_list('test@example.com') + scheme = lookup(config.passwords.password_scheme.upper()) + self._mlist.moderator_password = make_secret('super secret', scheme) + self._rule = approved.Approved() + self._msg_text_template = """\ +From: anne@example.com +To: test@example.com +Subject: A Message with non-ascii body +Message-ID: <ant> +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="AAA" + +--AAA +Content-Type: application/x-ignore + +{0}: not the password +The above line will be ignored. + +--AAA +Content-Type: text/plain + +{0}: {1} +An important message. + +""" + + def test_approved_pseudo_header_mime(self): + msg = mfs(self._msg_text_template.format('Approved', 'super secret')) + result = self._rule.check(self._mlist, msg, {}) + self.assertTrue(result) + + def test_approve_pseudo_header_mime(self): + msg = mfs(self._msg_text_template.format('Approve', 'super secret')) + result = self._rule.check(self._mlist, msg, {}) + self.assertTrue(result) + + def test_x_approved_pseudo_header_mime(self): + msg = mfs(self._msg_text_template.format('X-Approved', 'super secret')) + result = self._rule.check(self._mlist, msg, {}) + self.assertTrue(result) + + def test_x_approve_pseudo_header_mime(self): + msg = mfs(self._msg_text_template.format('X-Approve', 'super secret')) + result = self._rule.check(self._mlist, msg, {}) + self.assertTrue(result) + + def test_approved_pseudo_header_wrong_password_mime(self): + msg = mfs(self._msg_text_template.format('Approved', 'not password')) + result = self._rule.check(self._mlist, msg, {}) + self.assertFalse(result) + + def test_approve_pseudo_header_wrong_password_mime(self): + msg = mfs(self._msg_text_template.format('Approve', 'not password')) + result = self._rule.check(self._mlist, msg, {}) + self.assertFalse(result) + + def test_x_approved_pseudo_header_wrong_password_mime(self): + msg = mfs(self._msg_text_template.format('X-Approved', 'not password')) + result = self._rule.check(self._mlist, msg, {}) + self.assertFalse(result) + + def test_x_approve_pseudo_header_wrong_password_mime(self): + msg = mfs(self._msg_text_template.format('X-Approve', 'not password')) + result = self._rule.check(self._mlist, msg, {}) + self.assertFalse(result) + + def test_removes_approved_pseudo_header_mime(self): + msg = mfs(self._msg_text_template.format('Approved', 'super secret')) + self._rule.check(self._mlist, msg, {}) + self.assertFalse('Approved' in msg.get_payload(1).get_payload()) + + def test_removes_approve_pseudo_header_mime(self): + msg = mfs(self._msg_text_template.format('Approve', 'super secret')) + self._rule.check(self._mlist, msg, {}) + self.assertFalse('Approve' in msg.get_payload(1).get_payload()) + + def test_removes_x_approved_pseudo_header_mime(self): + msg = mfs(self._msg_text_template.format('X-Approved', 'super secret')) + self._rule.check(self._mlist, msg, {}) + self.assertFalse('X-Approved' in msg.get_payload(1).get_payload()) + + def test_removes_x_approve_pseudo_header_mime(self): + msg = mfs(self._msg_text_template.format('X-Approve', 'super secret')) + self._rule.check(self._mlist, msg, {}) + self.assertFalse('X-Approve' in msg.get_payload(1).get_payload()) + + def test_removes_approved_pseudo_header_wrong_password_mime(self): + msg = mfs(self._msg_text_template.format('Approved', 'not password')) + self._rule.check(self._mlist, msg, {}) + self.assertFalse('Approved' in msg.get_payload(1).get_payload()) + + def test_removes_approve_pseudo_header_wrong_password_mime(self): + msg = mfs(self._msg_text_template.format('Approve', 'not password')) + self._rule.check(self._mlist, msg, {}) + self.assertFalse('Approve' in msg.get_payload(1).get_payload()) + + def test_removes_x_approved_pseudo_header_wrong_password_mime(self): + msg = mfs(self._msg_text_template.format('X-Approved', 'not password')) + self._rule.check(self._mlist, msg, {}) + self.assertFalse('X-Approved' in msg.get_payload(1).get_payload()) + + def test_removes_x_approve_pseudo_header_wrong_password_mime(self): + msg = mfs(self._msg_text_template.format('X-Approve', 'not password')) + self._rule.check(self._mlist, msg, {}) + self.assertFalse('X-Approve' in msg.get_payload(1).get_payload()) + + + +class TestApprovedNonASCII(unittest.TestCase): + """Test the approved handler with non-ascii messages.""" + + layer = ConfigLayer + + def setUp(self): + self._mlist = create_list('test@example.com') self._rule = approved.Approved() self._msg = mfs("""\ From: anne@example.com @@ -53,7 +408,6 @@ Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable This is a message body with a non-ascii character =E4 - """) def test_nonascii_body_missing_header(self): |
