Esempio n. 1
0
class TestRequests(unittest.TestCase):
    layer = ConfigLayer

    def setUp(self):
        self._mlist = create_list('*****@*****.**')
        self._requests_db = IListRequests(self._mlist)
        self._msg = mfs("""\
From: [email protected]
To: [email protected]
Subject: Something
Message-ID: <alpha>

Something else.
""")

    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>')

    def test_hold_with_bogus_type(self):
        # Calling hold_request() with a bogus request type is an error.
        with self.assertRaises(TypeError) as cm:
            self._requests_db.hold_request(5, 'foo')
        self.assertEqual(cm.exception.args[0], 5)

    def test_delete_missing_request(self):
        # Trying to delete a missing request is an error.
        with self.assertRaises(KeyError) as cm:
            self._requests_db.delete_request(801)
        self.assertEqual(cm.exception.args[0], 801)

    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))
Esempio n. 2
0
def hold_subscription(mlist, address, display_name, password, mode, language):
    data = dict(when=now().isoformat(),
                address=address,
                display_name=display_name,
                password=password,
                delivery_mode=mode.name,
                language=language)
    # Now hold this request.  We'll use the address as the key.
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(
        RequestType.subscription, address, data)
    vlog.info('%s: held subscription request from %s',
              mlist.fqdn_listname, address)
    # Possibly notify the administrator in default list language
    if mlist.admin_immed_notify:
        subject = _(
            'New subscription request to $mlist.display_name from $address')
        text = make('subauth.txt',
                    mailing_list=mlist,
                    username=address,
                    listname=mlist.fqdn_listname,
                    admindb_url=mlist.script_url('admindb'),
                    )
        # This message should appear to come from the <list>-owner so as
        # to avoid any useless bounce processing.
        msg = UserNotification(
            mlist.owner_address, mlist.owner_address,
            subject, text, mlist.preferred_language)
        msg.send(mlist, tomoderators=True)
    return request_id
Esempio n. 3
0
def hold_unsubscription(mlist, email):
    data = dict(email=email)
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(RequestType.unsubscription, email,
                                         data)
    vlog.info('%s: held unsubscription request from %s', mlist.fqdn_listname,
              email)
    # Possibly notify the administrator of the hold
    if mlist.admin_immed_notify:
        subject = _(
            'New unsubscription request from $mlist.display_name by $email')
        template = getUtility(ITemplateLoader).get(
            'list:admin:action:unsubscribe', mlist)
        text = wrap(
            expand(
                template,
                mlist,
                dict(
                    # For backward compatibility.
                    mailing_list=mlist,
                    member=email,
                    email=email,
                )))
        # This message should appear to come from the <list>-owner so as
        # to avoid any useless bounce processing.
        msg = UserNotification(mlist.owner_address, mlist.owner_address,
                               subject, text, mlist.preferred_language)
        msg.send(mlist)
    return request_id
Esempio n. 4
0
def hold_subscription(mlist, address, display_name, password, mode, language):
    data = dict(when=now().isoformat(),
                address=address,
                display_name=display_name,
                password=password,
                delivery_mode=mode.name,
                language=language)
    # Now hold this request.  We'll use the address as the key.
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(RequestType.subscription, address,
                                         data)
    vlog.info('%s: held subscription request from %s', mlist.fqdn_listname,
              address)
    # Possibly notify the administrator in default list language
    if mlist.admin_immed_notify:
        subject = _(
            'New subscription request to $mlist.display_name from $address')
        text = make(
            'subauth.txt',
            mailing_list=mlist,
            username=address,
            listname=mlist.fqdn_listname,
            admindb_url=mlist.script_url('admindb'),
        )
        # This message should appear to come from the <list>-owner so as
        # to avoid any useless bounce processing.
        msg = UserNotification(mlist.owner_address, mlist.owner_address,
                               subject, text, mlist.preferred_language)
        msg.send(mlist, tomoderators=True)
    return request_id
Esempio n. 5
0
 def test_request_is_not_held_message(self):
     requests = IListRequests(self._mlist)
     with transaction():
         request_id = requests.hold_request(RequestType.subscription, 'foo')
     with self.assertRaises(HTTPError) as cm:
         call_api('http://localhost:9001/3.0/lists/ant.example.com'
                  '/held/{}'.format(request_id))
     self.assertEqual(cm.exception.code, 404)
Esempio n. 6
0
 def test_request_is_not_held_message(self):
     requests = IListRequests(self._mlist)
     with transaction():
         request_id = requests.hold_request(RequestType.subscription, 'foo')
     with self.assertRaises(HTTPError) as cm:
         call_api('http://localhost:9001/3.0/lists/ant.example.com'
                  '/held/{}'.format(request_id))
     self.assertEqual(cm.exception.code, 404)
Esempio n. 7
0
class TestRequests(unittest.TestCase):
    layer = ConfigLayer

    def setUp(self):
        self._mlist = create_list('*****@*****.**')
        self._requests_db = IListRequests(self._mlist)
        self._msg = mfs("""\
From: [email protected]
To: [email protected]
Subject: Something
Message-ID: <alpha>

Something else.
""")

    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>')

    def test_hold_with_bogus_type(self):
        # Calling hold_request() with a bogus request type is an error.
        with self.assertRaises(TypeError) as cm:
            self._requests_db.hold_request(5, 'foo')
        self.assertEqual(cm.exception.args[0], 5)

    def test_delete_missing_request(self):
        # Trying to delete a missing request is an error.
        with self.assertRaises(KeyError) as cm:
            self._requests_db.delete_request(801)
        self.assertEqual(cm.exception.args[0], 801)
Esempio n. 8
0
def hold_message(mlist, msg, msgdata=None, reason=None):
    """Hold a message for moderator approval.

    The message is added to the mailing list's request database.

    :param mlist: The mailing list to hold the message on.
    :param msg: The message to hold.
    :param msgdata: Optional message metadata to hold.  If not given, a new
        metadata dictionary is created and held with the message.
    :param reason: Optional string reason why the message is being held.  If
        not given, the empty string is used.
    :return: An id used to handle the held message later.
    """
    if msgdata is None:
        msgdata = {}
    else:
        # Make a copy of msgdata so that subsequent changes won't corrupt the
        # request database.  TBD: remove the `filebase' key since this will
        # not be relevant when the message is resurrected.
        msgdata = msgdata.copy()
    if reason is None:
        reason = ''
    # Add the message to the message store.  It is required to have a
    # Message-ID header.
    message_id = msg.get('message-id')
    if message_id is None:
        msg['Message-ID'] = message_id = make_msgid()
    elif isinstance(message_id, bytes):
        message_id = message_id.decode('ascii')
    getUtility(IMessageStore).add(msg)
    # Prepare the message metadata with some extra information needed only by
    # the moderation interface.
    msgdata['_mod_message_id'] = message_id
    msgdata['_mod_listid'] = mlist.list_id
    msgdata['_mod_sender'] = msg.sender
    # The subject can sometimes be a Header instance.  Stringify it.
    msgdata['_mod_subject'] = str(msg.get('subject', _('(no subject)')))
    msgdata['_mod_reason'] = reason
    msgdata['_mod_hold_date'] = now().isoformat()
    # Now hold this request.  We'll use the message_id as the key.
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(RequestType.held_message, message_id,
                                         msgdata)
    return request_id
Esempio n. 9
0
def hold_message(mlist, msg, msgdata=None, reason=None):
    """Hold a message for moderator approval.

    The message is added to the mailing list's request database.

    :param mlist: The mailing list to hold the message on.
    :param msg: The message to hold.
    :param msgdata: Optional message metadata to hold.  If not given, a new
        metadata dictionary is created and held with the message.
    :param reason: Optional string reason why the message is being held.  If
        not given, the empty string is used.
    :return: An id used to handle the held message later.
    """
    if msgdata is None:
        msgdata = {}
    else:
        # Make a copy of msgdata so that subsequent changes won't corrupt the
        # request database.  TBD: remove the `filebase' key since this will
        # not be relevant when the message is resurrected.
        msgdata = msgdata.copy()
    if reason is None:
        reason = ''
    # Add the message to the message store.  It is required to have a
    # Message-ID header.
    message_id = msg.get('message-id')
    if message_id is None:
        msg['Message-ID'] = message_id = unicode(make_msgid())
    assert isinstance(message_id, unicode), (
        'Message-ID is not a unicode: %s' % message_id)
    getUtility(IMessageStore).add(msg)
    # Prepare the message metadata with some extra information needed only by
    # the moderation interface.
    msgdata['_mod_message_id'] = message_id
    msgdata['_mod_fqdn_listname'] = mlist.fqdn_listname
    msgdata['_mod_sender'] = msg.sender
    msgdata['_mod_subject'] = msg.get('subject', _('(no subject)'))
    msgdata['_mod_reason'] = reason
    msgdata['_mod_hold_date'] = now().isoformat()
    # Now hold this request.  We'll use the message_id as the key.
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(
        RequestType.held_message, message_id, msgdata)
    return request_id
Esempio n. 10
0
def hold_unsubscription(mlist, address):
    data = dict(address=address)
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(
        RequestType.unsubscription, address, data)
    vlog.info('%s: held unsubscription request from %s',
              mlist.fqdn_listname, address)
    # Possibly notify the administrator of the hold
    if mlist.admin_immed_notify:
        subject = _(
            'New unsubscription request from $mlist.display_name by $address')
        text = make('unsubauth.txt',
                    mailing_list=mlist,
                    address=address,
                    listname=mlist.fqdn_listname,
                    admindb_url=mlist.script_url('admindb'),
                    )
        # This message should appear to come from the <list>-owner so as
        # to avoid any useless bounce processing.
        msg = UserNotification(
            mlist.owner_address, mlist.owner_address,
            subject, text, mlist.preferred_language)
        msg.send(mlist, tomoderators=True)
    return request_id
Esempio n. 11
0
def hold_unsubscription(mlist, email):
    data = dict(email=email)
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(
        RequestType.unsubscription, email, data)
    vlog.info('%s: held unsubscription request from %s',
              mlist.fqdn_listname, email)
    # Possibly notify the administrator of the hold
    if mlist.admin_immed_notify:
        subject = _(
            'New unsubscription request from $mlist.display_name by $email')
        text = make('unsubauth.txt',
                    mailing_list=mlist,
                    email=email,
                    listname=mlist.fqdn_listname,
                    admindb_url=mlist.script_url('admindb'),
                    )
        # This message should appear to come from the <list>-owner so as
        # to avoid any useless bounce processing.
        msg = UserNotification(
            mlist.owner_address, mlist.owner_address,
            subject, text, mlist.preferred_language)
        msg.send(mlist, tomoderators=True)
    return request_id
class TestRequests(unittest.TestCase):
    layer = ConfigLayer

    def setUp(self):
        self._mlist = create_list('*****@*****.**')
        self._requests_db = IListRequests(self._mlist)
        self._msg = mfs("""\
From: [email protected]
To: [email protected]
Subject: Something
Message-ID: <alpha>

Something else.
""")

    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>')

    def test_hold_with_bogus_type(self):
        # Calling hold_request() with a bogus request type is an error.
        with self.assertRaises(TypeError) as cm:
            self._requests_db.hold_request(5, 'foo')
        self.assertEqual(cm.exception.args[0], 5)

    def test_delete_missing_request(self):
        # Trying to delete a missing request is an error.
        with self.assertRaises(KeyError) as cm:
            self._requests_db.delete_request(801)
        self.assertEqual(cm.exception.args[0], 801)

    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))

    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))
Esempio n. 13
0
class TestRequests(unittest.TestCase):
    layer = ConfigLayer

    def setUp(self):
        self._mlist = create_list('*****@*****.**')
        self._requests_db = IListRequests(self._mlist)
        self._msg = mfs("""\
From: [email protected]
To: [email protected]
Subject: Something
Message-ID: <alpha>

Something else.
""")

    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>')

    def test_hold_with_bogus_type(self):
        # Calling hold_request() with a bogus request type is an error.
        with self.assertRaises(TypeError) as cm:
            self._requests_db.hold_request(5, 'foo')
        self.assertEqual(cm.exception.args[0], 5)

    def test_delete_missing_request(self):
        # Trying to delete a missing request is an error.
        with self.assertRaises(KeyError) as cm:
            self._requests_db.delete_request(801)
        self.assertEqual(cm.exception.args[0], 801)

    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))

    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))