Beispiel #1
0
def handle_unsubscription(mlist, id, action, comment=None):
    requestdb = IListRequests(mlist)
    key, data = requestdb.get_request(id)
    address = data['address']
    if action is Action.defer:
        # Nothing to do.
        return
    elif action is Action.discard:
        # Nothing to do except delete the request from the database.
        pass
    elif action is Action.reject:
        key, data = requestdb.get_request(id)
        _refuse(mlist, _('Unsubscription request'), address,
                comment or _('[No reason given]'))
    elif action is Action.accept:
        key, data = requestdb.get_request(id)
        try:
            delete_member(mlist, address)
        except NotAMemberError:
            # User has already been unsubscribed.
            pass
        slog.info('%s: deleted %s', mlist.fqdn_listname, address)
    else:
        raise AssertionError('Unexpected action: {0}'.format(action))
    # Delete the request from the database.
    requestdb.delete_request(id)
Beispiel #2
0
def handle_unsubscription(mlist, id, action, comment=None):
    requestdb = IListRequests(mlist)
    key, data = requestdb.get_request(id)
    email = data['email']
    if action is Action.defer:
        # Nothing to do.
        return
    elif action is Action.discard:
        # Nothing to do except delete the request from the database.
        pass
    elif action is Action.reject:
        key, data = requestdb.get_request(id)
        send_rejection(
            mlist, _('Unsubscription request'), email,
            comment or _('[No reason given]'))
    elif action is Action.accept:
        key, data = requestdb.get_request(id)
        try:
            delete_member(mlist, email)
        except NotAMemberError:
            # User has already been unsubscribed.
            pass
        slog.info('%s: deleted %s', mlist.fqdn_listname, email)
    else:
        raise AssertionError('Unexpected action: {0}'.format(action))
    # Delete the request from the database.
    requestdb.delete_request(id)
Beispiel #3
0
def handle_ListDeletingEvent(event):
    if not isinstance(event, ListDeletingEvent):
        return
    # Get the held requests database for the mailing list.  Since the mailing
    # list is about to get deleted, we can delete all associated requests.
    requestsdb = IListRequests(event.mailing_list)
    for request in requestsdb.held_requests:
        requestsdb.delete_request(request.id)
Beispiel #4
0
def handle_ListDeletingEvent(event):
    if not isinstance(event, ListDeletingEvent):
        return
    # Get the held requests database for the mailing list.  Since the mailing
    # list is about to get deleted, we can delete all associated requests.
    requestsdb = IListRequests(event.mailing_list)
    for request in requestsdb.held_requests:
        requestsdb.delete_request(request.id)
Beispiel #5
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))
Beispiel #6
0
def handle_subscription(mlist, id, action, comment=None):
    requestdb = IListRequests(mlist)
    if action is Action.defer:
        # Nothing to do.
        return
    elif action is Action.discard:
        # Nothing to do except delete the request from the database.
        pass
    elif action is Action.reject:
        key, data = requestdb.get_request(id)
        _refuse(mlist, _('Subscription request'),
                data['address'],
                comment or _('[No reason given]'),
                lang=getUtility(ILanguageManager)[data['language']])
    elif action is Action.accept:
        key, data = requestdb.get_request(id)
        enum_value = data['delivery_mode'].split('.')[-1]
        delivery_mode = DeliveryMode(enum_value)
        address = data['address']
        display_name = data['display_name']
        language = getUtility(ILanguageManager)[data['language']]
        password = data['password']
        try:
            add_member(mlist, address, display_name, password,
                       delivery_mode, language)
        except AlreadySubscribedError:
            # The address got subscribed in some other way after the original
            # request was made and accepted.
            pass
        else:
            if mlist.send_welcome_message:
                send_welcome_message(mlist, address, language, delivery_mode)
            if mlist.admin_notify_mchanges:
                send_admin_subscription_notice(
                    mlist, address, display_name, language)
        slog.info('%s: new %s, %s %s', mlist.fqdn_listname,
                  delivery_mode, formataddr((display_name, address)),
                  'via admin approval')
    else:
        raise AssertionError('Unexpected action: {0}'.format(action))
    # Delete the request from the database.
    requestdb.delete_request(id)
Beispiel #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)
Beispiel #8
0
def handle_subscription(mlist, id, action, comment=None):
    requestdb = IListRequests(mlist)
    if action is Action.defer:
        # Nothing to do.
        return
    elif action is Action.discard:
        # Nothing to do except delete the request from the database.
        pass
    elif action is Action.reject:
        key, data = requestdb.get_request(id)
        _refuse(mlist,
                _('Subscription request'),
                data['address'],
                comment or _('[No reason given]'),
                lang=getUtility(ILanguageManager)[data['language']])
    elif action is Action.accept:
        key, data = requestdb.get_request(id)
        delivery_mode = DeliveryMode[data['delivery_mode']]
        address = data['address']
        display_name = data['display_name']
        language = getUtility(ILanguageManager)[data['language']]
        password = data['password']
        try:
            add_member(mlist, address, display_name, password, delivery_mode,
                       language)
        except AlreadySubscribedError:
            # The address got subscribed in some other way after the original
            # request was made and accepted.
            pass
        else:
            if mlist.admin_notify_mchanges:
                send_admin_subscription_notice(mlist, address, display_name,
                                               language)
        slog.info('%s: new %s, %s %s', mlist.fqdn_listname, delivery_mode,
                  formataddr((display_name, address)), 'via admin approval')
    else:
        raise AssertionError('Unexpected action: {0}'.format(action))
    # Delete the request from the database.
    requestdb.delete_request(id)
Beispiel #9
0
def handle_subscription(mlist, id, action, comment=None):
    requestdb = IListRequests(mlist)
    if action is Action.defer:
        # Nothing to do.
        return
    elif action is Action.discard:
        # Nothing to do except delete the request from the database.
        pass
    elif action is Action.reject:
        key, data = requestdb.get_request(id)
        send_rejection(
            mlist, _('Subscription request'),
            data['email'],
            comment or _('[No reason given]'),
            lang=getUtility(ILanguageManager)[data['language']])
    elif action is Action.accept:
        key, data = requestdb.get_request(id)
        delivery_mode = DeliveryMode[data['delivery_mode']]
        email = data['email']
        display_name = data['display_name']
        language = getUtility(ILanguageManager)[data['language']]
        try:
            add_member(
                mlist,
                RequestRecord(email, display_name, delivery_mode, language))
        except AlreadySubscribedError:
            # The address got subscribed in some other way after the original
            # request was made and accepted.
            pass
        slog.info('%s: new %s, %s %s', mlist.fqdn_listname,
                  delivery_mode, formataddr((display_name, email)),
                  'via admin approval')
    else:
        raise AssertionError('Unexpected action: {0}'.format(action))
    # Delete the request from the database.
    requestdb.delete_request(id)
Beispiel #10
0
def handle_message(mlist, id, action,
                   comment=None, preserve=False, forward=None):
    message_store = getUtility(IMessageStore)
    requestdb = IListRequests(mlist)
    key, msgdata = requestdb.get_request(id)
    # Handle the action.
    rejection = None
    message_id = msgdata['_mod_message_id']
    sender = msgdata['_mod_sender']
    subject = msgdata['_mod_subject']
    if action in (Action.defer, Action.hold):
        # Nothing to do, but preserve the message for later.
        preserve = True
    elif action is Action.discard:
        rejection = 'Discarded'
    elif action is Action.reject:
        rejection = 'Refused'
        member = mlist.members.get_member(sender)
        if member:
            language = member.preferred_language
        else:
            language = None
        _refuse(mlist, _('Posting of your message titled "$subject"'),
                sender, comment or _('[No reason given]'), language)
    elif action is Action.accept:
        # Start by getting the message from the message store.
        msg = message_store.get_message_by_id(message_id)
        # Delete moderation-specific entries from the message metadata.
        for key in msgdata.keys():
            if key.startswith('_mod_'):
                del msgdata[key]
        # Add some metadata to indicate this message has now been approved.
        msgdata['approved'] = True
        msgdata['moderator_approved'] = True
        # Calculate a new filebase for the approved message, otherwise
        # delivery errors will cause duplicates.
        if 'filebase' in msgdata:
            del msgdata['filebase']
        # Queue the file for delivery.  Trying to deliver the message directly
        # here can lead to a huge delay in web turnaround.  Log the moderation
        # and add a header.
        msg['X-Mailman-Approved-At'] = formatdate(
            time.mktime(now().timetuple()), localtime=True)
        vlog.info('held message approved, message-id: %s',
                  msg.get('message-id', 'n/a'))
        # Stick the message back in the incoming queue for further
        # processing.
        config.switchboards['pipeline'].enqueue(msg, _metadata=msgdata)
    else:
        raise AssertionError('Unexpected action: {0}'.format(action))
    # Forward the message.
    if forward:
        # Get a copy of the original message from the message store.
        msg = message_store.get_message_by_id(message_id)
        # It's possible the forwarding address list is a comma separated list
        # of display_name/address pairs.
        addresses = [addr[1] for addr in getaddresses(forward)]
        language = mlist.preferred_language
        if len(addresses) == 1:
            # If the address getting the forwarded message is a member of
            # the list, we want the headers of the outer message to be
            # encoded in their language.  Otherwise it'll be the preferred
            # language of the mailing list.  This is better than sending a
            # separate message per recipient.
            member = mlist.members.get_member(addresses[0])
            if member:
                language = member.preferred_language
        with _.using(language.code):
            fmsg = UserNotification(
                addresses, mlist.bounces_address,
                _('Forward of moderated message'),
                lang=language)
        fmsg.set_type('message/rfc822')
        fmsg.attach(msg)
        fmsg.send(mlist)
    # Delete the message from the message store if it is not being preserved.
    if not preserve:
        message_store.delete_message(message_id)
        requestdb.delete_request(id)
    # Log the rejection
    if rejection:
        note = """%s: %s posting:
\tFrom: %s
\tSubject: %s"""
        if comment:
            note += '\n\tReason: ' + comment
        vlog.info(note, mlist.fqdn_listname, rejection, sender, subject)
Beispiel #11
0
def handle_message(mlist, id, action, comment=None, forward=None):
    message_store = getUtility(IMessageStore)
    requestdb = IListRequests(mlist)
    key, msgdata = requestdb.get_request(id)
    # Handle the action.
    rejection = None
    message_id = msgdata['_mod_message_id']
    sender = msgdata['_mod_sender']
    subject = msgdata['_mod_subject']
    keep = False
    if action in (Action.defer, Action.hold):
        # Nothing to do, but preserve the message for later.
        keep = True
    elif action is Action.discard:
        rejection = 'Discarded'
    elif action is Action.reject:
        rejection = 'Refused'
        member = mlist.members.get_member(sender)
        if member:
            language = member.preferred_language
        else:
            language = None
        send_rejection(
            mlist, _('Posting of your message titled "$subject"'),
            sender, comment or _('[No reason given]'), language)
    elif action is Action.accept:
        # Start by getting the message from the message store.
        msg = message_store.get_message_by_id(message_id)
        # Delete moderation-specific entries from the message metadata.
        for key in list(msgdata):
            if key.startswith('_mod_'):
                del msgdata[key]
        # Add some metadata to indicate this message has now been approved.
        msgdata['approved'] = True
        msgdata['moderator_approved'] = True
        # Calculate a new filebase for the approved message, otherwise
        # delivery errors will cause duplicates.
        if 'filebase' in msgdata:
            del msgdata['filebase']
        # Queue the file for delivery.  Trying to deliver the message directly
        # here can lead to a huge delay in web turnaround.  Log the moderation
        # and add a header.
        msg['X-Mailman-Approved-At'] = formatdate(
            time.mktime(now().timetuple()), localtime=True)
        vlog.info('held message approved, message-id: %s',
                  msg.get('message-id', 'n/a'))
        # Stick the message back in the incoming queue for further
        # processing.
        config.switchboards['pipeline'].enqueue(msg, _metadata=msgdata)
    else:
        raise AssertionError('Unexpected action: {0}'.format(action))
    # Forward the message.
    if forward:
        # Get a copy of the original message from the message store.
        msg = message_store.get_message_by_id(message_id)
        # It's possible the forwarding address list is a comma separated list
        # of display_name/address pairs.
        addresses = [addr[1] for addr in getaddresses(forward)]
        language = mlist.preferred_language
        if len(addresses) == 1:
            # If the address getting the forwarded message is a member of
            # the list, we want the headers of the outer message to be
            # encoded in their language.  Otherwise it'll be the preferred
            # language of the mailing list.  This is better than sending a
            # separate message per recipient.
            member = mlist.members.get_member(addresses[0])
            if member:
                language = member.preferred_language
        with _.using(language.code):
            fmsg = UserNotification(
                addresses, mlist.bounces_address,
                _('Forward of moderated message'),
                lang=language)
        fmsg.set_type('message/rfc822')
        fmsg.attach(msg)
        fmsg.send(mlist)
    # Delete the request if it's not being kept.
    if not keep:
        requestdb.delete_request(id)
    # Log the rejection
    if rejection:
        note = """%s: %s posting:
\tFrom: %s
\tSubject: %s"""
        if comment:
            note += '\n\tReason: ' + comment
        vlog.info(note, mlist.fqdn_listname, rejection, sender, subject)
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))
Beispiel #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))