PageRenderTime 28ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/src/mailman/rules/tests/test_moderation.py

https://gitlab.com/mmhat/mailman
Python | 330 lines | 304 code | 8 blank | 18 comment | 0 complexity | 187d8cc7515589bd6fe3ec3b1640e0de MD5 | raw file
  1. # Copyright (C) 2014-2019 by the Free Software Foundation, Inc.
  2. #
  3. # This file is part of GNU Mailman.
  4. #
  5. # GNU Mailman is free software: you can redistribute it and/or modify it under
  6. # the terms of the GNU General Public License as published by the Free
  7. # Software Foundation, either version 3 of the License, or (at your option)
  8. # any later version.
  9. #
  10. # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
  11. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  13. # more details.
  14. #
  15. # You should have received a copy of the GNU General Public License along with
  16. # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
  17. """Test the `member-moderation` and `nonmember-moderation` rules."""
  18. import unittest
  19. from mailman.app.lifecycle import create_list
  20. from mailman.interfaces.action import Action
  21. from mailman.interfaces.bans import IBanManager
  22. from mailman.interfaces.member import MemberRole
  23. from mailman.interfaces.usermanager import IUserManager
  24. from mailman.rules import moderation
  25. from mailman.testing.helpers import (
  26. set_preferred, specialized_message_from_string as mfs)
  27. from mailman.testing.layers import ConfigLayer
  28. from zope.component import getUtility
  29. class TestModeration(unittest.TestCase):
  30. """Test the approved handler."""
  31. layer = ConfigLayer
  32. def setUp(self):
  33. self._mlist = create_list('test@example.com')
  34. def test_member_and_nonmember(self):
  35. user_manager = getUtility(IUserManager)
  36. anne = user_manager.create_address('anne@example.com')
  37. user_manager.create_address('bill@example.com')
  38. self._mlist.subscribe(anne, MemberRole.member)
  39. rule = moderation.NonmemberModeration()
  40. msg = mfs("""\
  41. From: anne@example.com
  42. Sender: bill@example.com
  43. To: test@example.com
  44. Subject: A test message
  45. Message-ID: <ant>
  46. MIME-Version: 1.0
  47. A message body.
  48. """)
  49. # Both Anne and Bill are in the message's senders list.
  50. self.assertIn('anne@example.com', msg.senders)
  51. self.assertIn('bill@example.com', msg.senders)
  52. # The NonmemberModeration rule should *not* hit, because even though
  53. # Bill is in the list of senders he is not a member of the mailing
  54. # list. Anne is also in the list of senders and she *is* a member, so
  55. # she takes precedence.
  56. result = rule.check(self._mlist, msg, {})
  57. self.assertFalse(result, 'NonmemberModeration rule should not hit')
  58. # After the rule runs, Bill becomes a non-member.
  59. bill_member = self._mlist.nonmembers.get_member('bill@example.com')
  60. self.assertIsNotNone(bill_member)
  61. # Bill is not a member.
  62. bill_member = self._mlist.members.get_member('bill@example.com')
  63. self.assertIsNone(bill_member)
  64. def test_moderation_reason(self):
  65. # When a message is moderated, a reason is added to the metadata.
  66. user_manager = getUtility(IUserManager)
  67. anne = user_manager.create_address('anne@example.com')
  68. msg = mfs("""\
  69. From: anne@example.com
  70. To: test@example.com
  71. Subject: A test message
  72. Message-ID: <ant>
  73. MIME-Version: 1.0
  74. A message body.
  75. """)
  76. # Anne is in the message's senders list.
  77. self.assertIn('anne@example.com', msg.senders)
  78. # Now run the rule.
  79. rule = moderation.NonmemberModeration()
  80. msgdata = {}
  81. result = rule.check(self._mlist, msg, msgdata)
  82. self.assertTrue(result, 'NonmemberModeration rule should hit')
  83. # The reason for moderation should be in the msgdata.
  84. reasons = msgdata['moderation_reasons']
  85. self.assertEqual(reasons, ['The message is not from a list member'])
  86. # Now make Anne a moderated member...
  87. anne_member = self._mlist.subscribe(anne, MemberRole.member)
  88. anne_member.moderation_action = Action.hold
  89. # ...and run the rule again.
  90. rule = moderation.MemberModeration()
  91. msgdata = {}
  92. result = rule.check(self._mlist, msg, msgdata)
  93. self.assertTrue(result, 'MemberModeration rule should hit')
  94. # The reason for moderation should be in the msgdata.
  95. reasons = msgdata['moderation_reasons']
  96. self.assertEqual(
  97. reasons, ['The message comes from a moderated member'])
  98. def test_these_nonmembers(self):
  99. # Test the legacy *_these_nonmembers attributes.
  100. user_manager = getUtility(IUserManager)
  101. actions = {
  102. 'anne@example.com': 'accept',
  103. 'bill@example.com': 'hold',
  104. 'chris@example.com': 'reject',
  105. 'dana@example.com': 'discard',
  106. '^anne-.*@example.com': 'accept',
  107. '^bill-.*@example.com': 'hold',
  108. '^chris-.*@example.com': 'reject',
  109. '^dana-.*@example.com': 'discard',
  110. }
  111. rule = moderation.NonmemberModeration()
  112. user_manager = getUtility(IUserManager)
  113. for address, action_name in actions.items():
  114. setattr(self._mlist,
  115. '{}_these_nonmembers'.format(action_name),
  116. [address])
  117. if address.startswith('^'):
  118. # It's a pattern, craft a proper address.
  119. address = address[1:].replace('.*', 'something')
  120. user_manager.create_address(address)
  121. msg = mfs("""\
  122. From: {}
  123. To: test@example.com
  124. Subject: A test message
  125. Message-ID: <ant>
  126. MIME-Version: 1.0
  127. A message body.
  128. """.format(address))
  129. msgdata = {}
  130. result = rule.check(self._mlist, msg, msgdata)
  131. self.assertTrue(result, 'NonmemberModeration rule should hit')
  132. self.assertIn('member_moderation_action', msgdata)
  133. self.assertEqual(
  134. msgdata['member_moderation_action'], action_name,
  135. 'Wrong action for {}: {}'.format(address, action_name))
  136. def test_nonmember_fallback_to_list_defaults(self):
  137. # https://gitlab.com/mailman/mailman/issues/189
  138. self._mlist.default_nonmember_action = Action.hold
  139. user_manager = getUtility(IUserManager)
  140. user_manager.create_address('anne@example.com')
  141. rule = moderation.NonmemberModeration()
  142. msg = mfs("""\
  143. From: anne@example.com
  144. To: test@example.com
  145. Subject: A test message
  146. Message-ID: <ant>
  147. MIME-Version: 1.0
  148. A message body.
  149. """)
  150. # First, the message should get held.
  151. msgdata = {}
  152. result = rule.check(self._mlist, msg, msgdata)
  153. self.assertTrue(result)
  154. self.assertEqual(msgdata['member_moderation_action'], 'hold')
  155. # As a side-effect, Anne has been added as a nonmember with a
  156. # moderation action that falls back to the list's default.
  157. anne = self._mlist.nonmembers.get_member('anne@example.com')
  158. self.assertIsNone(anne.moderation_action)
  159. # Then the list's default nonmember action is changed.
  160. self._mlist.default_nonmember_action = Action.discard
  161. msg.replace_header('Message-ID', '<bee>')
  162. # This time, the message should be discarded.
  163. result = rule.check(self._mlist, msg, msgdata)
  164. self.assertTrue(result)
  165. self.assertEqual(msgdata.get('member_moderation_action'), 'discard')
  166. def test_member_fallback_to_list_defaults(self):
  167. # https://gitlab.com/mailman/mailman/issues/189
  168. self._mlist.default_member_action = Action.accept
  169. user_manager = getUtility(IUserManager)
  170. anne = user_manager.create_address('anne@example.com')
  171. member = self._mlist.subscribe(anne, MemberRole.member)
  172. # Anne's moderation rule falls back to the list default.
  173. self.assertIsNone(member.moderation_action)
  174. rule = moderation.MemberModeration()
  175. msg = mfs("""\
  176. From: anne@example.com
  177. To: test@example.com
  178. Subject: A test message
  179. Message-ID: <ant>
  180. MIME-Version: 1.0
  181. A message body.
  182. """)
  183. # First, the message gets accepted.
  184. msgdata = {}
  185. result = rule.check(self._mlist, msg, msgdata)
  186. self.assertTrue(result)
  187. self.assertEqual(msgdata.get('member_moderation_action'), 'accept')
  188. # Then the list's default member action is changed.
  189. self._mlist.default_member_action = Action.hold
  190. msg.replace_header('Message-ID', '<bee>')
  191. # This time, the message is held.
  192. result = rule.check(self._mlist, msg, msgdata)
  193. self.assertTrue(result)
  194. self.assertEqual(msgdata.get('member_moderation_action'), 'hold')
  195. def test_linked_address_nonmembermoderation_misses(self):
  196. # Anne subscribes to a mailing list as a user with her preferred
  197. # address. She also has a secondary linked address, and she uses this
  198. # to post to the mailing list. The NonmemberModeration rule misses
  199. # because Anne is not a nonmember.
  200. user_manager = getUtility(IUserManager)
  201. anne = user_manager.create_user('anne@example.com')
  202. set_preferred(anne)
  203. self._mlist.subscribe(anne, MemberRole.member)
  204. anne.link(user_manager.create_address('anne.person@example.com'))
  205. rule = moderation.NonmemberModeration()
  206. msg = mfs("""\
  207. From: anne.person@example.com
  208. To: test@example.com
  209. Subject: A test message
  210. Message-ID: <ant>
  211. MIME-Version: 1.0
  212. A message body.
  213. """)
  214. result = rule.check(self._mlist, msg, {})
  215. self.assertFalse(result)
  216. def test_linked_address_membermoderation_hits(self):
  217. # Anne subscribes to a mailing list as a user with her preferred
  218. # address. She also has a secondary linked address, and she uses this
  219. # to post to the mailing list. The MemberModeration rule hits because
  220. # Anne is a member.
  221. self._mlist.default_member_action = Action.accept
  222. user_manager = getUtility(IUserManager)
  223. anne = user_manager.create_user('anne@example.com')
  224. set_preferred(anne)
  225. self._mlist.subscribe(anne, MemberRole.member)
  226. anne.link(user_manager.create_address('anne.person@example.com'))
  227. rule = moderation.MemberModeration()
  228. msg = mfs("""\
  229. From: anne.person@example.com
  230. To: test@example.com
  231. Subject: A test message
  232. Message-ID: <ant>
  233. MIME-Version: 1.0
  234. A message body.
  235. """)
  236. result = rule.check(self._mlist, msg, {})
  237. self.assertTrue(result)
  238. def test_banned_address_linked_to_user(self):
  239. # Anne is subscribed to a mailing list as a user with her preferred
  240. # address. She also has a secondary address which is banned and which
  241. # she uses to post to the mailing list. Both the MemberModeration and
  242. # NonmemberModeration rules miss because the posting address is
  243. # banned.
  244. user_manager = getUtility(IUserManager)
  245. anne = user_manager.create_user('anne@example.com')
  246. set_preferred(anne)
  247. self._mlist.subscribe(anne, MemberRole.member)
  248. anne.link(user_manager.create_address('anne.person@example.com'))
  249. IBanManager(self._mlist).ban('anne.person@example.com')
  250. msg = mfs("""\
  251. From: anne.person@example.com
  252. To: test@example.com
  253. Subject: A test message
  254. Message-ID: <ant>
  255. MIME-Version: 1.0
  256. A message body.
  257. """)
  258. rule = moderation.MemberModeration()
  259. result = rule.check(self._mlist, msg, {})
  260. self.assertFalse(result)
  261. rule = moderation.NonmemberModeration()
  262. result = rule.check(self._mlist, msg, {})
  263. self.assertFalse(result)
  264. def test_banned_sender_among_multiple_senders(self):
  265. # Two addresses are created, one of which is banned. Even though the
  266. # The Nonmember moderation rule misses if any of the banned addresses
  267. # appear in the 'senders' headers of the message.
  268. user_manager = getUtility(IUserManager)
  269. user_manager.create_address('anne@example.com')
  270. user_manager.create_address('bart@example.com')
  271. IBanManager(self._mlist).ban('bart@example.com')
  272. rule = moderation.NonmemberModeration()
  273. msg = mfs("""\
  274. From: anne@example.com
  275. Sender: bart@example.com
  276. To: test@example.com
  277. Subject: A test message
  278. Message-ID: <ant>
  279. MIME-Version: 1.0
  280. A message body.
  281. """)
  282. result = rule.check(self._mlist, msg, {})
  283. self.assertFalse(result)
  284. def test_no_senders(self):
  285. rule = moderation.NonmemberModeration()
  286. # Message without a From
  287. msg = mfs("""\
  288. To: test@example.com
  289. Subject: A test message
  290. Message-ID: <ant>
  291. MIME-Version: 1.0
  292. A message body.
  293. """)
  294. self.assertEqual(msg.senders, [])
  295. msgdata = {}
  296. # The NonmemberModeration rule should hit.
  297. result = rule.check(self._mlist, msg, msgdata)
  298. self.assertTrue(result, 'NonmemberModeration rule should hit')
  299. self.assertEqual(msgdata, {
  300. 'member_moderation_action': Action.hold,
  301. 'moderation_reasons': ['No sender was found in the message.'],
  302. 'moderation_sender': 'No sender',
  303. })