Beispiel #1
0
 def test_accepted_message_gets_posted(self):
     # A message that is accepted by the moderator should get posted to the
     # mailing list.  LP: #827697
     msgdata = dict(listname='*****@*****.**',
                    recipients=['*****@*****.**'])
     request_id = hold_message(self._mlist, self._msg, msgdata)
     handle_message(self._mlist, request_id, Action.accept)
     self._in.run()
     self._pipeline.run()
     self._out.run()
     messages = list(SMTPLayer.smtpd.messages)
     self.assertEqual(len(messages), 1)
     message = messages[0]
     # We don't need to test the entire posted message, just the bits that
     # prove it got sent out.
     self.assertTrue('x-mailman-version' in message)
     self.assertTrue('x-peer' in message)
     # The X-Mailman-Approved-At header has local timezone information in
     # it, so test that separately.
     self.assertEqual(message['x-mailman-approved-at'][:-5],
                      'Mon, 01 Aug 2005 07:49:23 ')
     del message['x-mailman-approved-at']
     # The Message-ID matches the original.
     self.assertEqual(message['message-id'], '<alpha>')
     # Anne sent the message and the mailing list received it.
     self.assertEqual(message['from'], '*****@*****.**')
     self.assertEqual(message['to'], '*****@*****.**')
     # The Subject header has the list's prefix.
     self.assertEqual(message['subject'], '[Test] hold me')
     # The list's -bounce address is the actual sender, and Bart is the
     # only actual recipient.  These headers are added by the testing
     # framework and don't show up in production.  They match the RFC 5321
     # envelope.
     self.assertEqual(message['x-mailfrom'], '*****@*****.**')
     self.assertEqual(message['x-rcptto'], '*****@*****.**')
 def test_accepted_message_gets_posted(self):
     # A message that is accepted by the moderator should get posted to the
     # mailing list.  LP: #827697
     msgdata = dict(listname='*****@*****.**',
                    recipients=['*****@*****.**'])
     request_id = hold_message(self._mlist, self._msg, msgdata)
     handle_message(self._mlist, request_id, Action.accept)
     self._in.run()
     self._pipeline.run()
     self._out.run()
     messages = list(SMTPLayer.smtpd.messages)
     self.assertEqual(len(messages), 1)
     message = messages[0]
     # We don't need to test the entire posted message, just the bits that
     # prove it got sent out.
     self.assertTrue('x-mailman-version' in message)
     self.assertTrue('x-peer' in message)
     # The X-Mailman-Approved-At header has local timezone information in
     # it, so test that separately.
     self.assertEqual(message['x-mailman-approved-at'][:-5],
                      'Mon, 01 Aug 2005 07:49:23 ')
     del message['x-mailman-approved-at']
     # The Message-ID matches the original.
     self.assertEqual(message['message-id'], '<alpha>')
     # Anne sent the message and the mailing list received it.
     self.assertEqual(message['from'], '*****@*****.**')
     self.assertEqual(message['to'], '*****@*****.**')
     # The Subject header has the list's prefix.
     self.assertEqual(message['subject'], '[Test] hold me')
     # The list's -bounce address is the actual sender, and Bart is the
     # only actual recipient.  These headers are added by the testing
     # framework and don't show up in production.  They match the RFC 5321
     # envelope.
     self.assertEqual(message['x-mailfrom'], '*****@*****.**')
     self.assertEqual(message['x-rcptto'], '*****@*****.**')
    def test_request_order(self):
        # Requests must be sorted in creation order.
        #
        # This test only "works" for PostgreSQL, in the sense that if you
        # remove the fix in ../requests.py, it will still pass in SQLite.
        # Apparently SQLite auto-sorts results by ID but PostgreSQL autosorts
        # by insertion time.  It's still worth keeping the test to prevent
        # regressions.
        #
        # We modify the auto-incremented ids by listening to SQLAlchemy's
        # flush event, and hacking all the _Request object id's to the next
        # value in a descending counter.
        request_ids = []
        counter = count(200, -1)

        def id_hacker(session, flush_context, instances):  # noqa: E306
            for instance in session.new:
                if isinstance(instance, _Request):
                    instance.id = next(counter)

        with before_flush(id_hacker):
            for index in range(10):
                msg = mfs(self._msg.as_string())
                msg.replace_header('Message-ID', '<alpha{}>'.format(index))
                request_ids.append(hold_message(self._mlist, msg))
            config.db.store.flush()
        # Make sure that our ID are not already sorted.
        self.assertNotEqual(request_ids, sorted(request_ids))
        # Get requests and check their order.
        requests = self._requests_db.of_type(RequestType.held_message)
        self.assertEqual([r.id for r in requests], sorted(request_ids))
Beispiel #4
0
 def test_request_order(self):
     # Requests must be sorted in creation order.
     #
     # This test only "works" for PostgreSQL, in the sense that if you
     # remove the fix in ../requests.py, it will still pass in SQLite.
     # Apparently SQLite auto-sorts results by ID but PostgreSQL autosorts
     # by insertion time.  It's still worth keeping the test to prevent
     # regressions.
     #
     # We modify the auto-incremented ids by listening to SQLAlchemy's
     # flush event, and hacking all the _Request object id's to the next
     # value in a descending counter.
     request_ids = []
     counter = count(200, -1)
     def id_hacker(session, flush_context, instances):   # noqa
         for instance in session.new:
             if isinstance(instance, _Request):
                 instance.id = next(counter)
     with before_flush(id_hacker):
         for index in range(10):
             msg = mfs(self._msg.as_string())
             msg.replace_header('Message-ID', '<alpha{}>'.format(index))
             request_ids.append(hold_message(self._mlist, msg))
         config.db.store.flush()
     # Make sure that our ID are not already sorted.
     self.assertNotEqual(request_ids, sorted(request_ids))
     # Get requests and check their order.
     requests = self._requests_db.of_type(RequestType.held_message)
     self.assertEqual([r.id for r in requests], sorted(request_ids))
Beispiel #5
0
 def test_preserving_disposition(self):
     # Preserving a message keeps it in the store.
     request_id = hold_message(self._mlist, self._msg)
     handle_message(self._mlist, request_id, Action.discard, preserve=True)
     message_store = getUtility(IMessageStore)
     preserved_message = message_store.get_message_by_id('<alpha>')
     self.assertEqual(preserved_message['message-id'], '<alpha>')
 def test_preserving_disposition(self):
     # Preserving a message keeps it in the store.
     request_id = hold_message(self._mlist, self._msg)
     handle_message(self._mlist, request_id, Action.discard, preserve=True)
     message_store = getUtility(IMessageStore)
     preserved_message = message_store.get_message_by_id('<alpha>')
     self.assertEqual(preserved_message['message-id'], '<alpha>')
Beispiel #7
0
 def test_handled_message_stays_in_store(self):
     # The message is still available in the store, even when it's been
     # disposed of.
     request_id = hold_message(self._mlist, self._msg)
     handle_message(self._mlist, request_id, Action.discard)
     self.assertEqual(self._request_db.count, 0)
     message = getUtility(IMessageStore).get_message_by_id('<alpha>')
     self.assertEqual(message['subject'], 'hold me')
Beispiel #8
0
 def test_bad_held_message_action(self):
     # POSTing to a held message with a bad action.
     held_id = hold_message(self._mlist, self._msg)
     url = "http://*****:*****@example.com/held/{0}"
     with self.assertRaises(HTTPError) as cm:
         call_api(url.format(held_id), {"action": "bogus"})
     self.assertEqual(cm.exception.code, 400)
     self.assertEqual(cm.exception.msg, "Cannot convert parameters: action")
Beispiel #9
0
 def test_bad_held_message_action(self):
     # POSTing to a held message with a bad action.
     held_id = hold_message(self._mlist, self._msg)
     url = 'http://*****:*****@example.com/held/{}'
     with self.assertRaises(HTTPError) as cm:
         call_api(url.format(held_id), {'action': 'bogus'})
     self.assertEqual(cm.exception.code, 400)
     self.assertEqual(cm.exception.msg, 'Cannot convert parameters: action')
Beispiel #10
0
 def test_view_malformed_held_message(self):
     # Opening a bad (i.e. bad structure) email and holding it.
     email_path = resource_filename('mailman.rest.tests.data',
                                    'bad_email.eml')
     with open(email_path, 'rb') as fp:
         msg = message_from_binary_file(fp)
     msg.sender = '*****@*****.**'
     with transaction():
         hold_message(self._mlist, msg)
     # Now trying to access held messages from REST API should not give
     # 500 server error if one of the messages can't be parsed properly.
     json, response = call_api(
         'http://*****:*****@example.com/held')
     self.assertEqual(response.status_code, 200)
     self.assertEqual(len(json['entries']), 1)
     self.assertEqual(json['entries'][0]['msg'],
                      'This message is defective')
Beispiel #11
0
 def test_survive_a_deleted_message(self):
     # When the message that should be deleted is not found in the store,
     # no error is raised.
     request_id = hold_message(self._mlist, self._msg)
     message_store = getUtility(IMessageStore)
     message_store.delete_message('<alpha>')
     handle_message(self._mlist, request_id, Action.discard)
     self.assertEqual(self._request_db.count, 0)
Beispiel #12
0
 def test_survive_a_deleted_message(self):
     # When the message that should be deleted is not found in the store,
     # no error is raised.
     request_id = hold_message(self._mlist, self._msg)
     message_store = getUtility(IMessageStore)
     message_store.delete_message('<alpha>')
     handle_message(self._mlist, request_id, Action.discard)
     self.assertEqual(self._request_db.count, 0)
Beispiel #13
0
 def test_handled_message_stays_in_store(self):
     # The message is still available in the store, even when it's been
     # disposed of.
     request_id = hold_message(self._mlist, self._msg)
     handle_message(self._mlist, request_id, Action.discard)
     self.assertEqual(self._request_db.count, 0)
     message = getUtility(IMessageStore).get_message_by_id('<alpha>')
     self.assertEqual(message['subject'], 'hold me')
Beispiel #14
0
 def test_lp_1031391(self):
     # LP: #1031391 msgdata['received_time'] gets added by the LMTP server.
     # The value is a datetime.  If this message gets held, it will break
     # pending requests since they require string keys and values.
     received_time = now()
     msgdata = dict(received_time=received_time)
     request_id = hold_message(self._mlist, self._msg, msgdata)
     key, data = self._request_db.get_request(request_id)
     self.assertEqual(data['received_time'], received_time)
 def test_lp_1031391(self):
     # LP: #1031391 msgdata['received_time'] gets added by the LMTP server.
     # The value is a datetime.  If this message gets held, it will break
     # pending requests since they require string keys and values.
     received_time = now()
     msgdata = dict(received_time=received_time)
     request_id = hold_message(self._mlist, self._msg, msgdata)
     key, data = self._request_db.get_request(request_id)
     self.assertEqual(data['received_time'], received_time)
Beispiel #16
0
 def test_list_held_messages(self):
     # We can view all the held requests.
     with transaction():
         held_id = hold_message(self._mlist, self._msg)
     json, response = call_api(
         'http://*****:*****@example.com/held')
     self.assertEqual(response.status_code, 200)
     self.assertEqual(json['total_size'], 1)
     self.assertEqual(json['entries'][0]['request_id'], held_id)
Beispiel #17
0
 def test_list_held_messages(self):
     # We can view all the held requests.
     with transaction():
         held_id = hold_message(self._mlist, self._msg)
     content, response = call_api(
         'http://*****:*****@example.com/held')
     self.assertEqual(response.status, 200)
     self.assertEqual(content['total_size'], 1)
     self.assertEqual(content['entries'][0]['request_id'], held_id)
Beispiel #18
0
 def test_cant_get_other_lists_holds(self):
     # Issue #161: It was possible to moderate a held message for another
     # list via the REST API.
     with transaction():
         held_id = hold_message(self._mlist, self._msg)
         create_list("*****@*****.**")
     with self.assertRaises(HTTPError) as cm:
         call_api("http://localhost:9001/3.0/lists/bee.example.com" "/held/{}".format(held_id))
     self.assertEqual(cm.exception.code, 404)
Beispiel #19
0
 def test_cant_get_other_lists_holds(self):
     # Issue #161: It was possible to moderate a held message for another
     # list via the REST API.
     with transaction():
         held_id = hold_message(self._mlist, self._msg)
         create_list('*****@*****.**')
     with self.assertRaises(HTTPError) as cm:
         call_api('http://localhost:9001/3.0/lists/bee.example.com'
                  '/held/{}'.format(held_id))
     self.assertEqual(cm.exception.code, 404)
Beispiel #20
0
 def test_bad_action(self):
     # POSTing to a held message with a bad action.
     held_id = hold_message(self._mlist, self._msg)
     url = "http://*****:*****@example.com/held/{0}"
     try:
         call_api(url.format(held_id), {"action": "bogus"})
     except HTTPError as exc:
         self.assertEqual(exc.code, 400)
         self.assertEqual(exc.msg, "Cannot convert parameters: action")
     else:
         raise AssertionError("Expected HTTPError")
 def test_hold_action_alias_for_defer(self):
     # In handle_message(), the 'hold' action is the same as 'defer' for
     # purposes of this API.
     request_id = hold_message(self._mlist, self._msg)
     handle_message(self._mlist, request_id, Action.defer)
     # The message is still in the pending requests.
     key, data = self._request_db.get_request(request_id)
     self.assertEqual(key, '<alpha>')
     handle_message(self._mlist, request_id, Action.hold)
     key, data = self._request_db.get_request(request_id)
     self.assertEqual(key, '<alpha>')
Beispiel #22
0
 def test_discard(self):
     # Discarding a message removes it from the moderation queue.
     with transaction():
         held_id = hold_message(self._mlist, self._msg)
     url = "http://*****:*****@example.com/held/{}".format(held_id)
     content, response = call_api(url, dict(action="discard"))
     self.assertEqual(response.status, 204)
     # Now it's gone.
     with self.assertRaises(HTTPError) as cm:
         call_api(url, dict(action="discard"))
     self.assertEqual(cm.exception.code, 404)
 def test_bad_held_message_action(self):
     # POSTing to a held message with a bad action.
     held_id = hold_message(self._mlist, self._msg)
     url = 'http://*****:*****@example.com/held/{}'
     with self.assertRaises(HTTPError) as cm:
         call_api(url.format(held_id), {'action': 'bogus'})
     self.assertEqual(cm.exception.code, 400)
     self.assertEqual(
         cm.exception.msg,
         'Invalid Parameter "action": Accepted Values are:'
         ' hold, reject, discard, accept, defer.')
Beispiel #24
0
 def test_forward(self):
     # We can forward the message to an email address.
     request_id = hold_message(self._mlist, self._msg)
     handle_message(self._mlist, request_id, Action.discard,
                    forward=['*****@*****.**'])
     # The forwarded message lives in the virgin queue.
     items = get_queue_messages('virgin', expected_count=1)
     self.assertEqual(str(items[0].msg['subject']),
                      'Forward of moderated message')
     self.assertEqual(items[0].msgdata['recipients'],
                      ['*****@*****.**'])
Beispiel #25
0
 def test_forward(self):
     # We can forward the message to an email address.
     request_id = hold_message(self._mlist, self._msg)
     handle_message(self._mlist, request_id, Action.discard,
                    forward=['*****@*****.**'])
     # The forwarded message lives in the virgin queue.
     items = get_queue_messages('virgin', expected_count=1)
     self.assertEqual(str(items[0].msg['subject']),
                      'Forward of moderated message')
     self.assertEqual(items[0].msgdata['recipients'],
                      ['*****@*****.**'])
Beispiel #26
0
 def test_hold_action_alias_for_defer(self):
     # In handle_message(), the 'hold' action is the same as 'defer' for
     # purposes of this API.
     request_id = hold_message(self._mlist, self._msg)
     handle_message(self._mlist, request_id, Action.defer)
     # The message is still in the pending requests.
     key, data = self._request_db.get_request(request_id)
     self.assertEqual(key, '<alpha>')
     handle_message(self._mlist, request_id, Action.hold)
     key, data = self._request_db.get_request(request_id)
     self.assertEqual(key, '<alpha>')
Beispiel #27
0
 def test_discard(self):
     # Discarding a message removes it from the moderation queue.
     with transaction():
         held_id = hold_message(self._mlist, self._msg)
     url = 'http://*****:*****@example.com/held/{}'.format(
         held_id)
     json, response = call_api(url, dict(action='discard'))
     self.assertEqual(response.status_code, 204)
     # Now it's gone.
     with self.assertRaises(HTTPError) as cm:
         call_api(url, dict(action='discard'))
     self.assertEqual(cm.exception.code, 404)
Beispiel #28
0
    def setUp(self):
        self._mlist = create_list('*****@*****.**')
        self._mlist2 = create_list('*****@*****.**')
        self._mlist.subscription_policy = SubscriptionPolicy.moderate
        self._mlist.unsubscription_policy = SubscriptionPolicy.moderate
        msg = mfs("""\
To: [email protected]
From: [email protected]
Subject: message 1

""")
        # Hold this message.
        hold_message(self._mlist, msg, {}, 'Non-member post')
        # And a second one too.
        msg2 = mfs("""\
To: [email protected]
From: [email protected]
Subject: message 2

""")
        hold_message(self._mlist, msg2, {}, 'Some other reason')
        usermanager = getUtility(IUserManager)
        submanager = ISubscriptionManager(self._mlist)
        # Generate held subscription.
        usera = usermanager.make_user('*****@*****.**')
        usera.addresses[0].verified_on = usera.addresses[0].registered_on
        usera.preferred_address = usera.addresses[0]
        submanager.register(usera)
        # Generate a held unsubscription.
        userb = usermanager.make_user('*****@*****.**')
        userb.addresses[0].verified_on = userb.addresses[0].registered_on
        userb.preferred_address = userb.addresses[0]
        submanager.register(userb,
                            pre_verified=True,
                            pre_confirmed=True,
                            pre_approved=True)
        submanager.unregister(userb)
        self._command = CliRunner()
 def test_get_request_with_type(self):
     # get_request() takes an optional request type.
     request_id = hold_message(self._mlist, self._msg)
     # Submit a request with a non-matching type.  This should return None
     # as if there were no matches.
     response = self._requests_db.get_request(request_id,
                                              RequestType.subscription)
     self.assertEqual(response, None)
     # Submit the same request with a matching type.
     key, data = self._requests_db.get_request(request_id,
                                               RequestType.held_message)
     self.assertEqual(key, '<alpha>')
     # It should also succeed with no optional request type given.
     key, data = self._requests_db.get_request(request_id)
     self.assertEqual(key, '<alpha>')
Beispiel #30
0
 def test_get_request_with_type(self):
     # get_request() takes an optional request type.
     request_id = hold_message(self._mlist, self._msg)
     # Submit a request with a non-matching type.  This should return None
     # as if there were no matches.
     response = self._requests_db.get_request(
         request_id, RequestType.subscription)
     self.assertEqual(response, None)
     # Submit the same request with a matching type.
     key, data = self._requests_db.get_request(
         request_id, RequestType.held_message)
     self.assertEqual(key, '<alpha>')
     # It should also succeed with no optional request type given.
     key, data = self._requests_db.get_request(request_id)
     self.assertEqual(key, '<alpha>')
Beispiel #31
0
 def test_subscription_request_as_held_message(self):
     # Provide the request id of a subscription request using the held
     # message API returns a not-found even though the request id is
     # in the database.
     held_id = hold_message(self._mlist, self._msg)
     subscribe_id = hold_subscription(
         self._mlist, "*****@*****.**", "Bart Person", "xyz", DeliveryMode.regular, "en"
     )
     config.db.store.commit()
     url = "http://*****:*****@example.com/held/{0}"
     with self.assertRaises(HTTPError) as cm:
         call_api(url.format(subscribe_id))
     self.assertEqual(cm.exception.code, 404)
     # But using the held_id returns a valid response.
     response, content = call_api(url.format(held_id))
     self.assertEqual(response["message_id"], "<alpha>")
    def test_handle_message_with_comment(self):
        self._msg = mfs("""\
From: [email protected]
To: [email protected]
Subject: Hello
Message-ID: <alpha>

Something else.
""")
        with transaction():
            held_id = hold_message(self._mlist, self._msg)
        json, response = call_api(
            'http://*****:*****@example.com'
            '/held/{}'.format(held_id),
            dict(action='reject', comment='Because I want to.'))
        self.assertEqual(response.status_code, 204)
 def test_preserve_and_forward(self):
     # We can both preserve and forward the message.
     request_id = hold_message(self._mlist, self._msg)
     handle_message(self._mlist, request_id, Action.discard,
                    preserve=True, forward=['*****@*****.**'])
     # The message is preserved in the store.
     message_store = getUtility(IMessageStore)
     preserved_message = message_store.get_message_by_id('<alpha>')
     self.assertEqual(preserved_message['message-id'], '<alpha>')
     # And the forwarded message lives in the virgin queue.
     messages = get_queue_messages('virgin')
     self.assertEqual(len(messages), 1)
     self.assertEqual(str(messages[0].msg['subject']),
                      'Forward of moderated message')
     self.assertEqual(messages[0].msgdata['recipients'],
                      ['*****@*****.**'])
Beispiel #34
0
 def test_subscription_request_as_held_message(self):
     # Provide the request id of a subscription request using the held
     # message API returns a not-found even though the request id is
     # in the database.
     held_id = hold_message(self._mlist, self._msg)
     subscribe_id = hold_subscription(self._mlist, '*****@*****.**',
                                      'Bart Person', 'xyz',
                                      DeliveryMode.regular, 'en')
     config.db.store.commit()
     url = 'http://*****:*****@example.com/held/{0}'
     with self.assertRaises(HTTPError) as cm:
         call_api(url.format(subscribe_id))
     self.assertEqual(cm.exception.code, 404)
     # But using the held_id returns a valid response.
     response, content = call_api(url.format(held_id))
     self.assertEqual(response['message_id'], '<alpha>')
Beispiel #35
0
    def test_subject_encoding_error(self):
        # GL#383: messages with badly encoded Subject headers crash the REST
        # server.
        self._msg = mfs("""\
From: [email protected]
To: [email protected]
Subject: =?GB2312?B?saa9o7fmtNPEpbVaQ2h1o6zDt7uoz+PX1L/guq7AtKGj?=
Message-ID: <alpha>

Something else.
""")
        with transaction():
            held_id = hold_message(self._mlist, self._msg)
        json, response = call_api(
            'http://*****:*****@example.com/held')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json['total_size'], 1)
        self.assertEqual(json['entries'][0]['request_id'], held_id)
Beispiel #36
0
 def test_preserve_and_forward(self):
     # We can both preserve and forward the message.
     request_id = hold_message(self._mlist, self._msg)
     handle_message(self._mlist,
                    request_id,
                    Action.discard,
                    preserve=True,
                    forward=['*****@*****.**'])
     # The message is preserved in the store.
     message_store = getUtility(IMessageStore)
     preserved_message = message_store.get_message_by_id('<alpha>')
     self.assertEqual(preserved_message['message-id'], '<alpha>')
     # And the forwarded message lives in the virgin queue.
     messages = get_queue_messages('virgin')
     self.assertEqual(len(messages), 1)
     self.assertEqual(str(messages[0].msg['subject']),
                      'Forward of moderated message')
     self.assertEqual(messages[0].msgdata['recipients'],
                      ['*****@*****.**'])
Beispiel #37
0
    def test_requests_are_deleted_when_mailing_list_is_deleted(self):
        # When a mailing list is deleted, its requests database is deleted
        # too, e.g. all its message hold requests (but not the messages
        # themselves).
        msg = specialized_message_from_string("""\
From: [email protected]
To: [email protected]
Subject: Hold me
Message-ID: <argon>

""")
        request_id = hold_message(self._ant, msg)
        getUtility(IListManager).delete(self._ant)
        # This is a hack.  ListRequests don't access self._mailinglist in
        # their get_request() method.
        requestsdb = IListRequests(self._bee)
        request = requestsdb.get_request(request_id)
        self.assertEqual(request, None)
        saved_message = getUtility(IMessageStore).get_message_by_id('<argon>')
        self.assertEqual(saved_message.as_string(), msg.as_string())
Beispiel #38
0
    def test_requests_are_deleted_when_mailing_list_is_deleted(self):
        # When a mailing list is deleted, its requests database is deleted
        # too, e.g. all its message hold requests (but not the messages
        # themselves).
        msg = specialized_message_from_string("""\
From: [email protected]
To: [email protected]
Subject: Hold me
Message-ID: <argon>

""")
        request_id = hold_message(self._ant, msg)
        getUtility(IListManager).delete(self._ant)
        # This is a hack.  ListRequests don't access self._mailinglist in
        # their get_request() method.
        requestsdb = IListRequests(self._bee)
        request = requestsdb.get_request(request_id)
        self.assertEqual(request, None)
        saved_message = getUtility(IMessageStore).get_message_by_id('<argon>')
        self.assertEqual(saved_message.as_string(), msg.as_string())
 def test_held_message_count(self):
     # Initially, the count should be zero.
     url = 'http://*****:*****@example.com/held/count'
     json, resp = call_api(url)
     self.assertEqual(resp.status_code, 200)
     self.assertEqual(json['count'], 0)
     # Now, verify that we get the number when a held message is added.
     with transaction():
         hold_message(self._mlist, self._msg)
     json, resp = call_api(url)
     self.assertEqual(resp.status_code, 200)
     self.assertEqual(json['count'], 1)
     # Hold some more to see if we get the right numbers.
     with transaction():
         hold_message(self._mlist, self._msg)
         hold_message(self._mlist, self._msg)
     json, resp = call_api(url)
     self.assertEqual(resp.status_code, 200)
     self.assertEqual(json['count'], 3)
 def test_non_preserving_disposition(self):
     # By default, disposed messages are not preserved.
     request_id = hold_message(self._mlist, self._msg)
     handle_message(self._mlist, request_id, Action.discard)
     message_store = getUtility(IMessageStore)
     self.assertIsNone(message_store.get_message_by_id('<alpha>'))
Beispiel #41
0
 def test_only_return_this_lists_requests(self):
     # Issue #161: get_requests() returns requests that are not specific to
     # the mailing list in question.
     request_id = hold_message(self._mlist, self._msg)
     bee = create_list('*****@*****.**')
     self.assertIsNone(IListRequests(bee).get_request(request_id))
Beispiel #42
0
    def _process(self, mlist, msg, msgdata):
        """See `TerminalChainBase`."""
        # Start by decorating the message with a header that contains a list
        # of all the rules that matched.  These metadata could be None or an
        # empty list.
        rule_hits = msgdata.get('rule_hits')
        if rule_hits:
            msg['X-Mailman-Rule-Hits'] = SEMISPACE.join(rule_hits)
        rule_misses = msgdata.get('rule_misses')
        if rule_misses:
            msg['X-Mailman-Rule-Misses'] = SEMISPACE.join(rule_misses)
        # Hold the message by adding it to the list's request database.
        request_id = hold_message(mlist, msg, msgdata, None)
        # Calculate a confirmation token to send to the author of the
        # message.
        pendable = HeldMessagePendable(type=HeldMessagePendable.PEND_KEY,
                                       id=request_id)
        token = getUtility(IPendings).add(pendable)
        # Get the language to send the response in.  If the sender is a
        # member, then send it in the member's language, otherwise send it in
        # the mailing list's preferred language.
        member = mlist.members.get_member(msg.sender)
        language = (member.preferred_language
                    if member else mlist.preferred_language)
        # A substitution dictionary for the email templates.
        charset = mlist.preferred_language.charset
        original_subject = msg.get('subject')
        if original_subject is None:
            original_subject = _('(no subject)')
        else:
            original_subject = oneline(original_subject, in_unicode=True)
        substitutions = dict(
            listname    = mlist.fqdn_listname,
            subject     = original_subject,
            sender      = msg.sender,
            reasons     = _compose_reasons(msgdata),
            )
        # At this point the message is held, but now we have to craft at least
        # two responses.  The first will go to the original author of the
        # message and it will contain the token allowing them to approve or
        # discard the message.  The second one will go to the moderators of
        # the mailing list, if the list is so configured.
        #
        # Start by possibly sending a response to the message author.  There
        # are several reasons why we might not go through with this.  If the
        # message was gated from NNTP, the author may not even know about this
        # list, so don't spam them.  If the author specifically requested that
        # acknowledgments not be sent, or if the message was bulk email, then
        # we do not send the response.  It's also possible that either the
        # mailing list, or the author (if they are a member) have been
        # configured to not send such responses.
        if (not msgdata.get('fromusenet') and
            can_acknowledge(msg) and
            mlist.respond_to_post_requests and
            autorespond_to_sender(mlist, msg.sender, language)):
            # We can respond to the sender with a message indicating their
            # posting was held.
            subject = _(
              'Your message to $mlist.fqdn_listname awaits moderator approval')
            send_language_code = msgdata.get('lang', language.code)
            text = make('postheld.txt',
                        mailing_list=mlist,
                        language=send_language_code,
                        **substitutions)
            adminaddr = mlist.bounces_address
            nmsg = UserNotification(
                msg.sender, adminaddr, subject, text,
                getUtility(ILanguageManager)[send_language_code])
            nmsg.send(mlist)
        # Now the message for the list moderators.  This one should appear to
        # come from <list>-owner since we really don't need to do bounce
        # processing on it.
        if mlist.admin_immed_notify:
            # Now let's temporarily set the language context to that which the
            # administrators are expecting.
            with _.using(mlist.preferred_language.code):
                language = mlist.preferred_language
                charset = language.charset
                substitutions['subject'] = original_subject
                # We need to regenerate or re-translate a few values in the
                # substitution dictionary.
                substitutions['reasons'] = _compose_reasons(msgdata, 55)
                # craft the admin notification message and deliver it
                subject = _(
                    '$mlist.fqdn_listname post from $msg.sender requires '
                    'approval')
                nmsg = UserNotification(mlist.owner_address,
                                        mlist.owner_address,
                                        subject, lang=language)
                nmsg.set_type('multipart/mixed')
                text = MIMEText(make('postauth.txt',
                                     mailing_list=mlist,
                                     wrap=False,
                                     **substitutions),
                                _charset=charset)
                dmsg = MIMEText(wrap(_("""\
If you reply to this message, keeping the Subject: header intact, Mailman will
discard the held message.  Do this if the message is spam.  If you reply to
this message and include an Approved: header with the list password in it, the
message will be approved for posting to the list.  The Approved: header can
also appear in the first line of the body of the reply.""")),
                                _charset=language.charset)
                dmsg['Subject'] = 'confirm ' + token
                dmsg['From'] = mlist.request_address
                dmsg['Date'] = formatdate(localtime=True)
                dmsg['Message-ID'] = make_msgid()
                nmsg.attach(text)
                nmsg.attach(MIMEMessage(msg))
                nmsg.attach(MIMEMessage(dmsg))
                nmsg.send(mlist, **dict(tomoderators=True))
        # Log the held message.  Log messages are not translated, so recast
        # the reasons in the English.
        with _.using('en'):
            reasons = _compose_reasons(msgdata)
            log.info('HOLD: %s post from %s held, message-id=%s: %s',
                     mlist.fqdn_listname, msg.sender,
                     msg.get('message-id', 'n/a'), SEMISPACE.join(reasons))
        notify(HoldEvent(mlist, msg, msgdata, self))
Beispiel #43
0
    def _process(self, mlist, msg, msgdata):
        """See `TerminalChainBase`."""
        # Start by decorating the message with a header that contains a list
        # of all the rules that matched.  These metadata could be None or an
        # empty list.
        rule_hits = msgdata.get('rule_hits')
        if rule_hits:
            msg['X-Mailman-Rule-Hits'] = SEMISPACE.join(rule_hits)
        rule_misses = msgdata.get('rule_misses')
        if rule_misses:
            msg['X-Mailman-Rule-Misses'] = SEMISPACE.join(rule_misses)
        # Hold the message by adding it to the list's request database.
        request_id = hold_message(mlist, msg, msgdata, None)
        # Calculate a confirmation token to send to the author of the
        # message.
        pendable = HeldMessagePendable(id=request_id)
        token = getUtility(IPendings).add(pendable)
        # Get the language to send the response in.  If the sender is a
        # member, then send it in the member's language, otherwise send it in
        # the mailing list's preferred language.
        member = mlist.members.get_member(msg.sender)
        language = (member.preferred_language
                    if member else mlist.preferred_language)
        # A substitution dictionary for the email templates.
        charset = mlist.preferred_language.charset
        original_subject = msg.get('subject')
        if original_subject is None:
            original_subject = _('(no subject)')
        else:
            # This must be encoded to the mailing list's perferred charset,
            # ignoring incompatible characters, otherwise when creating the
            # notification messages, we could get a Unicode error.
            oneline_subject = oneline(original_subject, in_unicode=True)
            bytes_subject = oneline_subject.encode(charset, 'replace')
            original_subject = bytes_subject.decode(charset)
        substitutions = dict(
            subject=original_subject,
            sender_email=msg.sender,
            reasons=_compose_reasons(msgdata),
            # For backward compatibility.
            sender=msg.sender,
            )
        # At this point the message is held, but now we have to craft at least
        # two responses.  The first will go to the original author of the
        # message and it will contain the token allowing them to approve or
        # discard the message.  The second one will go to the moderators of
        # the mailing list, if the list is so configured.
        #
        # Start by possibly sending a response to the message author.  There
        # are several reasons why we might not go through with this.  If the
        # message was gated from NNTP, the author may not even know about this
        # list, so don't spam them.  If the author specifically requested that
        # acknowledgments not be sent, or if the message was bulk email, then
        # we do not send the response.  It's also possible that either the
        # mailing list, or the author (if they are a member) have been
        # configured to not send such responses.
        if (not msgdata.get('fromusenet') and
                can_acknowledge(msg) and
                mlist.respond_to_post_requests and
                autorespond_to_sender(mlist, msg.sender, language)):
            # We can respond to the sender with a message indicating their
            # posting was held.
            subject = _(
              'Your message to $mlist.fqdn_listname awaits moderator approval')
            send_language_code = msgdata.get('lang', language.code)
            template = getUtility(ITemplateLoader).get(
                'list:user:notice:hold', mlist,
                language=send_language_code)
            text = wrap(expand(template, mlist, dict(
                language=send_language_code,
                **substitutions)))
            adminaddr = mlist.bounces_address
            nmsg = UserNotification(
                msg.sender, adminaddr, subject, text,
                getUtility(ILanguageManager)[send_language_code])
            nmsg.send(mlist)
        # Now the message for the list moderators.  This one should appear to
        # come from <list>-owner since we really don't need to do bounce
        # processing on it.
        if mlist.admin_immed_notify:
            # Now let's temporarily set the language context to that which the
            # administrators are expecting.
            with _.using(mlist.preferred_language.code):
                language = mlist.preferred_language
                charset = language.charset
                substitutions['subject'] = original_subject
                # We need to regenerate or re-translate a few values in the
                # substitution dictionary.
                substitutions['reasons'] = _compose_reasons(msgdata, 55)
                # craft the admin notification message and deliver it
                subject = _(
                    '$mlist.fqdn_listname post from $msg.sender requires '
                    'approval')
                nmsg = UserNotification(mlist.owner_address,
                                        mlist.owner_address,
                                        subject, lang=language)
                nmsg.set_type('multipart/mixed')
                template = getUtility(ITemplateLoader).get(
                    'list:admin:action:post', mlist)
                text = MIMEText(expand(template, mlist, substitutions),
                                _charset=charset)
                dmsg = MIMEText(wrap(_("""\
If you reply to this message, keeping the Subject: header intact, Mailman will
discard the held message.  Do this if the message is spam.  If you reply to
this message and include an Approved: header with the list password in it, the
message will be approved for posting to the list.  The Approved: header can
also appear in the first line of the body of the reply.""")),
                                _charset=language.charset)
                dmsg['Subject'] = 'confirm ' + token
                dmsg['From'] = mlist.request_address
                dmsg['Date'] = formatdate(localtime=True)
                dmsg['Message-ID'] = make_msgid()
                nmsg.attach(text)
                nmsg.attach(MIMEMessage(msg))
                nmsg.attach(MIMEMessage(dmsg))
                nmsg.send(mlist, **dict(tomoderators=True))
        # Log the held message.  Log messages are not translated, so recast
        # the reasons in the English.
        with _.using('en'):
            reasons = msgdata.get('moderation_reasons', ['N/A'])
            log.info('HOLD: %s post from %s held, message-id=%s: %s',
                     mlist.fqdn_listname, msg.sender,
                     msg.get('message-id', 'n/a'), SEMISPACE.join(reasons))
        notify(HoldEvent(mlist, msg, msgdata, self))
 def test_only_return_this_lists_requests(self):
     # Issue #161: get_requests() returns requests that are not specific to
     # the mailing list in question.
     request_id = hold_message(self._mlist, self._msg)
     bee = create_list('*****@*****.**')
     self.assertIsNone(IListRequests(bee).get_request(request_id))
Beispiel #45
0
 def test_non_preserving_disposition(self):
     # By default, disposed messages are not preserved.
     request_id = hold_message(self._mlist, self._msg)
     handle_message(self._mlist, request_id, Action.discard)
     message_store = getUtility(IMessageStore)
     self.assertIsNone(message_store.get_message_by_id('<alpha>'))