# Copyright (C) 2012-2017 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 . """Test the `approved` handler.""" import os import unittest from mailman.app.lifecycle import create_list from mailman.config import config from mailman.rules import approved from mailman.testing.helpers import ( configuration, specialized_message_from_string as mfs) from mailman.testing.layers import ConfigLayer class TestApproved(unittest.TestCase): """Test the approved handler.""" layer = ConfigLayer def setUp(self): self._mlist = create_list('test@example.com') self._mlist.moderator_password = config.password_context.encrypt( 'super secret') self._rule = approved.Approved() self._msg = mfs("""\ From: anne@example.com To: test@example.com Subject: A Message with non-ascii body Message-ID: 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) def test_no_list_password(self): self._mlist.moderator_password = None self._msg['Approved'] = 'super secret' result = self._rule.check(self._mlist, self._msg, {}) self.assertFalse(result) class TestApprovedPseudoHeader(unittest.TestCase): """Test the approved handler.""" layer = ConfigLayer def setUp(self): self._mlist = create_list('test@example.com') self._mlist.moderator_password = config.password_context.encrypt( 'super secret') self._rule = approved.Approved() self._msg = mfs("""\ From: anne@example.com To: test@example.com Subject: A Message with non-ascii body Message-ID: 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') self._mlist.moderator_password = config.password_context.encrypt( 'super secret') 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: 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._mlist.moderator_password = config.password_context.encrypt( 'super secret') self._rule = approved.Approved() def test_nonascii_body_missing_header(self): # When the message body contains non-ascii, the rule should not throw # unicode errors. LP: #949924. msg = mfs("""\ From: anne@example.com To: test@example.com Subject: A Message with non-ascii body Message-ID: MIME-Version: 1.0 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable This is a message body with a non-ascii character =E4 """) result = self._rule.check(self._mlist, msg, {}) self.assertFalse(result) def test_unknown_charset(self): # When the charset is unknown, the rule should not crash. GL: #203 msg = mfs("""\ From: anne@example.com To: test@example.com Subject: A Message with non-ascii body Message-ID: MIME-Version: 1.0 Content-Type: text/plain; charset=unknown-8bit Content-Disposition: inline Content-Transfer-Encoding: quoted-printable This is a message body with a non-ascii character =E4 """) result = self._rule.check(self._mlist, msg, {}) self.assertFalse(result) class TestPasswordHashMigration(unittest.TestCase): """Test that password hashing migrations work.""" # http://packages.python.org/passlib/lib/passlib.context-tutorial.html#integrating-hash-migration layer = ConfigLayer def setUp(self): self._mlist = create_list('test@example.com') # The default testing hash algorithm is "roundup_plaintext" which # yields hashed passwords of the form: {plaintext}abc # # Migration is automatically supported when a more modern password # hash is chosen after the original password is set. As long as the # old password still validates, the migration happens automatically. self._mlist.moderator_password = config.password_context.encrypt( 'super secret') self._rule = approved.Approved() self._msg = mfs("""\ From: anne@example.com To: test@example.com Subject: A Message with non-ascii body Message-ID: MIME-Version: 1.0 A message body. """) def test_valid_password_migrates(self): # Now that the moderator password is set, change the default password # hashing algorithm. When the old password is validated, it will be # automatically migrated to the new hash. self.assertEqual(self._mlist.moderator_password, '{plaintext}super secret') config_file = os.path.join(config.VAR_DIR, 'passlib.config') # XXX passlib seems to choose the default hashing scheme even if it is # deprecated. The default scheme is either specified explicitly, or # is the first in this list. This seems like a bug. with open(config_file, 'w') as fp: print("""\ [passlib] schemes = roundup_plaintext, plaintext default = plaintext deprecated = roundup_plaintext """, file=fp) with configuration('passwords', configuration=config_file): self._msg['Approved'] = 'super secret' result = self._rule.check(self._mlist, self._msg, {}) self.assertTrue(result) self.assertEqual(self._mlist.moderator_password, 'super secret') def test_invalid_password_does_not_migrate(self): # Now that the moderator password is set, change the default password # hashing algorithm. When the old password is invalid, it will not be # automatically migrated to the new hash. self.assertEqual(self._mlist.moderator_password, '{plaintext}super secret') config_file = os.path.join(config.VAR_DIR, 'passlib.config') # XXX passlib seems to choose the default hashing scheme even if it is # deprecated. The default scheme is either specified explicitly, or # is the first in this list. This seems like a bug. with open(config_file, 'w') as fp: print("""\ [passlib] schemes = roundup_plaintext, plaintext default = plaintext deprecated = roundup_plaintext """, file=fp) with configuration('passwords', configuration=config_file): self._msg['Approved'] = 'not the password' result = self._rule.check(self._mlist, self._msg, {}) self.assertFalse(result) self.assertEqual(self._mlist.moderator_password, '{plaintext}super secret') class TestApprovedNoTextPlainPart(unittest.TestCase): """Test the approved handler with HTML-only messages.""" layer = ConfigLayer def setUp(self): self._mlist = create_list('test@example.com') self._rule = approved.Approved() def test_no_text_plain_part(self): # When the message body only contains HTML, the rule should not throw # AttributeError: 'NoneType' object has no attribute 'get_payload' # LP: #1158721 msg = mfs("""\ From: anne@example.com To: test@example.com Subject: HTML only email Message-ID: MIME-Version: 1.0 Content-Type: text/html; charset="Windows-1251" Content-Transfer-Encoding: 7bit

This message contains only HTML, no plain/text part

""") result = self._rule.check(self._mlist, msg, {}) self.assertFalse(result)