summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/docs/NEWS.rst2
-rw-r--r--src/mailman/model/mailinglist.py6
-rw-r--r--src/mailman/rules/approved.py7
-rw-r--r--src/mailman/rules/docs/approved.rst297
-rw-r--r--src/mailman/rules/tests/test_approved.py358
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):