Ejemplo n.º 1
0
 def process(self, mlist, msg, msgdata, arguments, results):
     """See `IEmailCommand`."""
     email = msg.sender
     if not email:
         print(_('$self.name: No valid email address found to unsubscribe'),
               file=results)
         return ContinueProcessing.no
     user_manager = getUtility(IUserManager)
     user = user_manager.get_user(email)
     if user is None:
         print(_('No registered user for email address: $email'),
               file=results)
         return ContinueProcessing.no
     # The address that the -leave command was sent from, must be verified.
     # Otherwise you could link a bogus address to anyone's account, and
     # then send a leave command from that address.
     if user_manager.get_address(email).verified_on is None:
         print(_('Invalid or unverified email address: $email'),
               file=results)
         return ContinueProcessing.no
     already_left = msgdata.setdefault('leaves', set())
     for user_address in user.addresses:
         # Only recognize verified addresses.
         if user_address.verified_on is None:
             continue
         member = mlist.members.get_member(user_address.email)
         if member is not None:
             break
     else:
         # There are two possible situations.  Either none of the user's
         # addresses are subscribed to this mailing list, or this command
         # email *already* unsubscribed the user from the mailing list.
         # E.g. if a message was sent to the -leave address and it
         # contained the 'leave' command.  Don't send a bogus response in
         # this case, just ignore subsequent leaves of the same address.
         if email not in already_left:
             print(_('$self.name: $email is not a member of '
                     '$mlist.fqdn_listname'),
                   file=results)
             return ContinueProcessing.no
     if email in already_left:
         return ContinueProcessing.yes
     # Ignore any subsequent 'leave' commands.
     already_left.add(email)
     manager = ISubscriptionManager(mlist)
     token, token_owner, member = manager.unregister(user_address)
     person = formataddr((user.display_name, email))  # noqa
     if member is None:
         print(_('$person left $mlist.fqdn_listname'), file=results)
     else:
         print(_('Confirmation email sent to $person to leave'
                 ' $mlist.fqdn_listname'),
               file=results)
     return ContinueProcessing.yes
Ejemplo n.º 2
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()
Ejemplo n.º 3
0
 def on_delete(self, request, response):
     """Delete the member (i.e. unsubscribe)."""
     # Leaving a list is a bit different than deleting a moderator or
     # owner.  Handle the former case first.  For now too, we will not send
     # an admin or user notification.
     if self._member is None:
         not_found(response)
         return
     mlist = getUtility(IListManager).get_by_list_id(self._member.list_id)
     if self._member.role is MemberRole.member:
         try:
             values = Validator(
                 pre_confirmed=as_boolean,
                 pre_approved=as_boolean,
                 _optional=('pre_confirmed', 'pre_approved'),
             )(request)
         except ValueError as error:
             bad_request(response, str(error))
             return
         manager = ISubscriptionManager(mlist)
         # XXX(maxking): For backwards compat, we are going to keep
         # pre-confirmed to be "True" by defualt instead of "False", that it
         # should be. Any, un-authenticated requests should manually specify
         # that it is *not* confirmed by the user.
         if 'pre_confirmed' in values:
             pre_confirmed = values.get('pre_confirmed')
         else:
             pre_confirmed = True
         token, token_owner, member = manager.unregister(
             self._member.address,
             pre_approved=values.get('pre_approved'),
             pre_confirmed=pre_confirmed)
         if member is None:
             assert token is None
             assert token_owner is TokenOwner.no_one
             no_content(response)
         else:
             assert token is not None
             content = dict(token=token, token_owner=token_owner.name)
             accepted(response, etag(content))
     else:
         self._member.unsubscribe()
         no_content(response)
class TestSubscriptionModeration(unittest.TestCase):
    layer = RESTLayer
    maxDiff = None

    def setUp(self):
        with transaction():
            self._mlist = create_list('*****@*****.**')
            self._mlist.unsubscription_policy = SubscriptionPolicy.moderate
            self._registrar = ISubscriptionManager(self._mlist)
            manager = getUtility(IUserManager)
            self._anne = manager.create_address('*****@*****.**',
                                                'Anne Person')
            self._bart = manager.make_user('*****@*****.**', 'Bart Person')
            set_preferred(self._bart)

    def test_no_such_list(self):
        # Try to get the requests of a nonexistent list.
        with self.assertRaises(HTTPError) as cm:
            call_api('http://*****:*****@example.com/'
                     'requests')
        self.assertEqual(cm.exception.code, 404)

    def test_no_such_subscription_token(self):
        # Bad request when the token is not in the database.
        with self.assertRaises(HTTPError) as cm:
            call_api('http://*****:*****@example.com/'
                     'requests/missing')
        self.assertEqual(cm.exception.code, 404)

    def test_bad_subscription_action(self):
        # POSTing to a held message with a bad action.
        token, token_owner, member = self._registrar.register(self._anne)
        # Anne's subscription request got held.
        self.assertIsNone(member)
        # Let's try to handle her request, but with a bogus action.
        url = 'http://*****:*****@example.com/requests/{}'
        with self.assertRaises(HTTPError) as cm:
            call_api(url.format(token), dict(action='bogus', ))
        self.assertEqual(cm.exception.code, 400)
        self.assertEqual(
            cm.exception.msg,
            'Invalid Parameter "action": Accepted Values are:'
            ' hold, reject, discard, accept, defer.')

    def test_list_held_requests(self):
        # We can view all the held requests.
        with transaction():
            token_1, token_owner, member = self._registrar.register(self._anne)
            # Anne's subscription request got held.
            self.assertIsNotNone(token_1)
            self.assertIsNone(member)
            token_2, token_owner, member = self._registrar.register(self._bart)
            self.assertIsNotNone(token_2)
            self.assertIsNone(member)
        json, response = call_api(
            'http://*****:*****@example.com/requests')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json['total_size'], 2)
        tokens = set(entry['token'] for entry in json['entries'])
        self.assertEqual(tokens, {token_1, token_2})
        emails = set(entry['email'] for entry in json['entries'])
        self.assertEqual(emails, {'*****@*****.**', '*****@*****.**'})

    def test_list_held_requests_with_owner(self):
        with transaction():
            token_1, token_owner, member = self._registrar.register(self._anne)
            # Anne's subscription request got held.
            self.assertIsNotNone(token_1)
            self.assertIsNone(member)
            token_2, token_owner, member = self._registrar.register(self._bart)
            self.assertIsNotNone(token_2)
            self.assertIsNone(member)
        json, response = call_api(
            'http://*****:*****@example.com/requests'
            '?token_owner=moderator')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json['total_size'], 0)
        json, response = call_api(
            'http://*****:*****@example.com/requests'
            '?token_owner=subscriber')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json['total_size'], 2)

    def test_list_held_requests_count(self):
        with transaction():
            token_1, token_owner, member = self._registrar.register(self._anne)
            # Anne's subscription request got held.
            self.assertIsNotNone(token_1)
            self.assertIsNone(member)
            token_2, token_owner, member = self._registrar.register(self._bart)
            self.assertIsNotNone(token_2)
            self.assertIsNone(member)
        json, response = call_api(
            'http://*****:*****@example.com/requests'
            '/count')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json['count'], 2)
        json, response = call_api(
            'http://*****:*****@example.com/requests'
            '/count?token_owner=moderator')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json['count'], 0)
        json, response = call_api(
            'http://*****:*****@example.com/requests'
            '/count?token_owner=subscriber')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json['count'], 2)

    def test_list_held_unsubscription_request(self):
        with transaction():
            # First, subscribe Anne and then trigger an un-subscription.
            self._mlist.subscribe(self._bart)
            token, token_owner, member = self._registrar.unregister(self._bart)
            # Anne's un-subscription request got held.
            self.assertIsNotNone(token)
            self.assertIsNotNone(member)
        json, response = call_api(
            'http://*****:*****@example.com/requests'
            '?request_type=unsubscription')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(json['entries']), 1)
        # Individual request can then be fetched.
        url = 'http://*****:*****@example.com/requests/{}'
        json, response = call_api(url.format(token))
        self.assertEqual(json['token'], token)
        self.assertEqual(json['token_owner'], token_owner.name)
        self.assertEqual(json['email'], '*****@*****.**')
        self.assertEqual(json['type'], 'unsubscription')
        # Bart should still be a Member.
        self.assertIsNotNone(
            self._mlist.members.get_member('*****@*****.**'))
        # Now, accept the request.
        json, response, call_api(url.format(token), dict(action='accept', ))
        self.assertEqual(response.status_code, 200)
        # Now, the Member should be un-subscribed.
        self.assertIsNone(self._mlist.members.get_member('*****@*****.**'))

    def test_unsubscription_request_count(self):
        with transaction():
            # First, subscribe Anne and then trigger an un-subscription.
            self._mlist.subscribe(self._bart)
            token, token_owner, member = self._registrar.unregister(self._bart)
            # Anne's un-subscription request got held.
            self.assertIsNotNone(token)
            self.assertIsNotNone(member)
        json, response = call_api(
            'http://*****:*****@example.com/requests/count'
            '?request_type=unsubscription')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json['count'], 1)

    def test_individual_request(self):
        # We can view an individual request.
        with transaction():
            token, token_owner, member = self._registrar.register(self._anne)
            # Anne's subscription request got held.
            self.assertIsNotNone(token)
            self.assertIsNone(member)
        url = 'http://*****:*****@example.com/requests/{}'
        json, response = call_api(url.format(token))
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json['token'], token)
        self.assertEqual(json['token_owner'], token_owner.name)
        self.assertEqual(json['email'], '*****@*****.**')

    def test_accept(self):
        # POST to the request to accept it.
        with transaction():
            token, token_owner, member = self._registrar.register(self._anne)
        # Anne's subscription request got held.
        self.assertIsNone(member)
        url = 'http://*****:*****@example.com/requests/{}'
        json, response = call_api(url.format(token), dict(action='accept', ))
        self.assertEqual(response.status_code, 204)
        # Anne is a member.
        self.assertEqual(
            self._mlist.members.get_member('*****@*****.**').address,
            self._anne)
        # The request URL no longer exists.
        with self.assertRaises(HTTPError) as cm:
            call_api(url.format(token), dict(action='accept', ))
        self.assertEqual(cm.exception.code, 404)

    def test_accept_already_subscribed(self):
        # POST to a subscription request, but the user is already subscribed.
        with transaction():
            token, token_owner, member = self._registrar.register(self._anne)
            # Make Anne already a member.
            self._mlist.subscribe(self._anne)
        # Accept the pending subscription, which raises an error.
        url = 'http://*****:*****@example.com'
                '/requests/bogus', dict(action='accept'))
        self.assertEqual(cm.exception.code, 404)

    def test_accept_by_moderator_clears_request_queue(self):
        # After accepting a message held for moderator approval, there are no
        # more requests to handle.
        #
        # We start with nothing in the queue.
        json, response = call_api(
            'http://*****:*****@example.com/requests')
        self.assertEqual(json['total_size'], 0)
        # Anne tries to subscribe to a list that only requests moderator
        # approval.
        with transaction():
            self._mlist.subscription_policy = SubscriptionPolicy.moderate
            token, token_owner, member = self._registrar.register(
                self._anne, pre_verified=True, pre_confirmed=True)
        # There's now one request in the queue, and it's waiting on moderator
        # approval.
        json, response = call_api(
            'http://*****:*****@example.com/requests')
        self.assertEqual(json['total_size'], 1)
        entry = json['entries'][0]
        self.assertEqual(entry['token_owner'], 'moderator')
        self.assertEqual(entry['email'], '*****@*****.**')
        # The moderator approves the request.
        url = 'http://*****:*****@example.com/requests/{}'
        json, response = call_api(url.format(token), {'action': 'accept'})
        self.assertEqual(response.status_code, 204)
        # And now the request queue is empty.
        json, response = call_api(
            'http://*****:*****@example.com/requests')
        self.assertEqual(json['total_size'], 0)

    def test_discard(self):
        # POST to the request to discard it.
        with transaction():
            token, token_owner, member = self._registrar.register(self._anne)
        # Anne's subscription request got held.
        self.assertIsNone(member)
        url = 'http://*****:*****@example.com/requests/{}'
        json, response = call_api(url.format(token), dict(action='discard', ))
        self.assertEqual(response.status_code, 204)
        # Anne is not a member.
        self.assertIsNone(self._mlist.members.get_member('*****@*****.**'))
        # The request URL no longer exists.
        with self.assertRaises(HTTPError) as cm:
            call_api(url.format(token), dict(action='discard', ))
        self.assertEqual(cm.exception.code, 404)

    def test_defer(self):
        # Defer the decision for some other moderator.
        with transaction():
            token, token_owner, member = self._registrar.register(self._anne)
        # Anne's subscription request got held.
        self.assertIsNone(member)
        url = 'http://*****:*****@example.com/requests/{}'
        json, response = call_api(url.format(token), dict(action='defer', ))
        self.assertEqual(response.status_code, 204)
        # Anne is not a member.
        self.assertIsNone(self._mlist.members.get_member('*****@*****.**'))
        # The request URL still exists.
        json, response = call_api(url.format(token), dict(action='defer', ))
        self.assertEqual(response.status_code, 204)
        # And now we can accept it.
        json, response = call_api(url.format(token), dict(action='accept', ))
        self.assertEqual(response.status_code, 204)
        # Anne is a member.
        self.assertEqual(
            self._mlist.members.get_member('*****@*****.**').address,
            self._anne)
        # The request URL no longer exists.
        with self.assertRaises(HTTPError) as cm:
            call_api(url.format(token), dict(action='accept', ))
        self.assertEqual(cm.exception.code, 404)

    def test_defer_bad_token(self):
        # Try to accept a request with a bogus token.
        with self.assertRaises(HTTPError) as cm:
            call_api(
                'http://*****:*****@example.com'
                '/requests/bogus', dict(action='defer'))
        self.assertEqual(cm.exception.code, 404)

    def test_reject(self):
        # POST to the request to reject it.  This leaves a bounce message in
        # the virgin queue.
        with transaction():
            token, token_owner, member = self._registrar.register(self._anne)
        # Anne's subscription request got held.
        self.assertIsNone(member)
        # Clear out the virgin queue, which currently contains the
        # confirmation message sent to Anne.
        get_queue_messages('virgin')
        url = 'http://*****:*****@example.com/requests/{}'
        json, response = call_api(url.format(token), dict(action='reject', ))
        self.assertEqual(response.status_code, 204)
        # Anne is not a member.
        self.assertIsNone(self._mlist.members.get_member('*****@*****.**'))
        # The request URL no longer exists.
        with self.assertRaises(HTTPError) as cm:
            call_api(url.format(token), dict(action='reject', ))
        self.assertEqual(cm.exception.code, 404)
        # And the rejection message to Anne is now in the virgin queue.
        items = get_queue_messages('virgin')
        self.assertEqual(len(items), 1)
        message = items[0].msg
        self.assertEqual(message['From'], '*****@*****.**')
        self.assertEqual(message['To'], '*****@*****.**')
        self.assertEqual(message['Subject'],
                         'Request to mailing list "Ant" rejected')

    def test_reject_bad_token(self):
        # Try to accept a request with a bogus token.
        with self.assertRaises(HTTPError) as cm:
            call_api(
                'http://*****:*****@example.com'
                '/requests/bogus', dict(action='reject'))
        self.assertEqual(cm.exception.code, 404)

    def test_reject_with_reason(self):
        # Try to reject a request with an additional comment/reason.
        # POST to the request to reject it.  This leaves a bounce message in
        # the virgin queue.
        with transaction():
            token, token_owner, member = self._registrar.register(self._anne)
        # Anne's subscription request got held.
        self.assertIsNone(member)
        # Clear out the virgin queue, which currently contains the
        # confirmation message sent to Anne.
        get_queue_messages('virgin')
        url = 'http://*****:*****@example.com/requests/{}'
        reason = 'You are not authorized!'
        json, response = call_api(url.format(token),
                                  dict(action='reject', reason=reason))
        self.assertEqual(response.status_code, 204)
        # And the rejection message to Anne is now in the virgin queue.
        items = get_queue_messages('virgin')
        self.assertEqual(len(items), 1)
        message = items[0].msg
        self.assertEqual(message['From'], '*****@*****.**')
        self.assertEqual(message['To'], '*****@*****.**')
        self.assertEqual(message['Subject'],
                         'Request to mailing list "Ant" rejected')
        self.assertTrue(reason in message.as_string())

    def test_hold_keeps_holding(self):
        # POST to the request to continue holding it.
        with transaction():
            token, token_owner, member = self._registrar.register(self._anne)
        # Anne's subscription request got held.
        self.assertIsNone(member)
        # Clear out the virgin queue, which currently contains the
        # confirmation message sent to Anne.
        get_queue_messages('virgin')
        url = 'http://*****:*****@example.com/requests/{}'
        json, response = call_api(url.format(token), dict(action='hold', ))
        self.assertEqual(response.status_code, 204)
        # Anne is not a member.
        self.assertIsNone(self._mlist.members.get_member('*****@*****.**'))
        # The request URL still exists.
        json, response = call_api(url.format(token), dict(action='defer', ))
        self.assertEqual(response.status_code, 204)

    def test_subscribe_other_role_with_no_preferred_address(self):
        with transaction():
            cate = getUtility(IUserManager).create_user('*****@*****.**')
        with self.assertRaises(HTTPError) as cm:
            call_api(
                'http://*****:*****@example.com')
        with self.assertRaises(HTTPError) as cm:
            call_api(
                'http://*****:*****@example.com',
                    'role': 'moderator',
                })
        self.assertEqual(cm.exception.code, 400)
        self.assertEqual(cm.exception.reason, 'Membership is banned')