Esempio n. 1
0
class IndividualRequest(_ModerationBase):
    """Resource for moderating a membership change."""

    def __init__(self, mlist, token):
        super().__init__()
        self._mlist = mlist
        self._registrar = IRegistrar(self._mlist)
        self._token = token

    def on_get(self, request, response):
        # Get the pended record associated with this token, if it exists in
        # the pending table.
        try:
            resource = self._resource_as_dict(self._token)
            assert resource is not None, resource
        except LookupError:
            not_found(response)
            return
        okay(response, etag(resource))

    def on_post(self, request, response):
        try:
            validator = Validator(action=enum_validator(Action))
            arguments = validator(request)
        except ValueError as error:
            bad_request(response, str(error))
            return
        action = arguments['action']
        if action is Action.defer:
            # At least see if the token is in the database.
            pendable = self._pendings.confirm(self._token, expunge=False)
            if pendable is None:
                not_found(response)
            else:
                no_content(response)
        elif action is Action.accept:
            try:
                self._registrar.confirm(self._token)
            except LookupError:
                not_found(response)
            else:
                no_content(response)
        elif action is Action.discard:
            # At least see if the token is in the database.
            pendable = self._pendings.confirm(self._token, expunge=True)
            if pendable is None:
                not_found(response)
            else:
                no_content(response)
        elif action is Action.reject:
            # Like discard but sends a rejection notice to the user.
            pendable = self._pendings.confirm(self._token, expunge=True)
            if pendable is None:
                not_found(response)
            else:
                no_content(response)
                send_rejection(
                    self._mlist, _('Subscription request'),
                    pendable['email'],
                    _('[No reason given]'))
Esempio n. 2
0
class IndividualRequest(_ModerationBase):
    """Resource for moderating a membership change."""
    def __init__(self, mlist, token):
        super().__init__()
        self._mlist = mlist
        self._registrar = IRegistrar(self._mlist)
        self._token = token

    def on_get(self, request, response):
        # Get the pended record associated with this token, if it exists in
        # the pending table.
        try:
            resource = self._resource_as_dict(self._token)
            assert resource is not None, resource
        except LookupError:
            not_found(response)
            return
        okay(response, etag(resource))

    def on_post(self, request, response):
        try:
            validator = Validator(action=enum_validator(Action))
            arguments = validator(request)
        except ValueError as error:
            bad_request(response, str(error))
            return
        action = arguments['action']
        if action in (Action.defer, Action.hold):
            # At least see if the token is in the database.
            pendable = self._pendings.confirm(self._token, expunge=False)
            if pendable is None:
                not_found(response)
            else:
                no_content(response)
        elif action is Action.accept:
            try:
                self._registrar.confirm(self._token)
            except LookupError:
                not_found(response)
            except AlreadySubscribedError:
                conflict(response, 'Already subscribed')
            else:
                no_content(response)
        elif action is Action.discard:
            # At least see if the token is in the database.
            pendable = self._pendings.confirm(self._token, expunge=True)
            if pendable is None:
                not_found(response)
            else:
                no_content(response)
        else:
            assert action is Action.reject, action
            # Like discard but sends a rejection notice to the user.
            pendable = self._pendings.confirm(self._token, expunge=True)
            if pendable is None:
                not_found(response)
            else:
                no_content(response)
                send_rejection(self._mlist, _('Subscription request'),
                               pendable['email'], _('[No reason given]'))
Esempio n. 3
0
 def setUp(self):
     with transaction():
         self._mlist = create_list('*****@*****.**')
         self._registrar = IRegistrar(self._mlist)
         manager = getUtility(IUserManager)
         self._anne = manager.create_address('*****@*****.**',
                                             'Anne Person')
         self._bart = manager.make_user('*****@*****.**', 'Bart Person')
         set_preferred(self._bart)
Esempio n. 4
0
 def setUp(self):
     self._commandq = config.switchboards['command']
     self._runner = make_testable_runner(CommandRunner, 'command')
     with transaction():
         # Register a subscription requiring confirmation.
         self._mlist = create_list('*****@*****.**')
         self._mlist.send_welcome_message = False
         anne = getUtility(IUserManager).create_address('*****@*****.**')
         registrar = IRegistrar(self._mlist)
         self._token, token_owner, member = registrar.register(anne)
Esempio n. 5
0
 def setUp(self):
     self._commandq = config.switchboards['command']
     self._runner = make_testable_runner(CommandRunner, 'command')
     with transaction():
         # Register a subscription requiring confirmation.
         self._mlist = create_list('*****@*****.**')
         self._mlist.send_welcome_message = False
         anne = getUtility(IUserManager).create_address('*****@*****.**')
         registrar = IRegistrar(self._mlist)
         self._token, token_owner, member = registrar.register(anne)
Esempio n. 6
0
 def setUp(self):
     with transaction():
         self._mlist = create_list('*****@*****.**')
         self._registrar = IRegistrar(self._mlist)
         manager = getUtility(IUserManager)
         self._anne = manager.create_address('*****@*****.**',
                                             'Anne Person')
         self._bart = manager.make_user('*****@*****.**', 'Bart Person')
         preferred = list(self._bart.addresses)[0]
         preferred.verified_on = now()
         self._bart.preferred_address = preferred
Esempio n. 7
0
 def process(self, mlist, msg, msgdata, arguments, results):
     """See `IEmailCommand`."""
     # The token must be in the arguments.
     if len(arguments) == 0:
         print(_('No confirmation token found'), file=results)
         return ContinueProcessing.no
     # Make sure we don't try to confirm the same token more than once.
     token = arguments[0]
     tokens = getattr(results, 'confirms', set())
     if token in tokens:
         # Do not try to confirm this one again.
         return ContinueProcessing.yes
     tokens.add(token)
     results.confirms = tokens
     try:
         token, token_owner, member = IRegistrar(mlist).confirm(token)
         if token is None:
             assert token_owner is TokenOwner.no_one, token_owner
             assert member is not None, member
             succeeded = True
         else:
             assert token_owner is not TokenOwner.no_one, token_owner
             assert member is None, member
             succeeded = False
     except LookupError:
         # The token must not exist in the database.
         succeeded = False
     if succeeded:
         print(_('Confirmed'), file=results)
         return ContinueProcessing.yes
     print(_('Confirmation token did not match'), file=results)
     return ContinueProcessing.no
Esempio n. 8
0
 def setUp(self):
     self._mlist = create_list('*****@*****.**')
     anne = getUtility(IUserManager).create_address('*****@*****.**',
                                                    'Anne Person')
     self._token, token_owner, member = IRegistrar(
         self._mlist).register(anne)
     self._command = Confirm()
     # Clear the virgin queue.
     get_queue_messages('virgin')
Esempio n. 9
0
 def setUp(self):
     with transaction():
         self._mlist = create_list("*****@*****.**")
         self._registrar = IRegistrar(self._mlist)
         manager = getUtility(IUserManager)
         self._anne = manager.create_address("*****@*****.**", "Anne Person")
         self._bart = manager.make_user("*****@*****.**", "Bart Person")
         preferred = list(self._bart.addresses)[0]
         preferred.verified_on = now()
         self._bart.preferred_address = preferred
Esempio n. 10
0
 def setUp(self):
     with transaction():
         self._mlist = create_list('*****@*****.**')
         self._registrar = IRegistrar(self._mlist)
         manager = getUtility(IUserManager)
         self._anne = manager.create_address(
             '*****@*****.**', 'Anne Person')
         self._bart = manager.make_user(
             '*****@*****.**', 'Bart Person')
         set_preferred(self._bart)
Esempio n. 11
0
 def test_duplicate_pending_subscription(self):
     # Issue #199 - a member's subscription is already pending and they try
     # to subscribe again.
     registrar = IRegistrar(self._mlist)
     with transaction():
         self._mlist.subscription_policy = SubscriptionPolicy.moderate
         anne = self._usermanager.create_address('*****@*****.**')
         token, token_owner, member = registrar.register(
             anne, pre_verified=True, pre_confirmed=True)
         self.assertEqual(token_owner, TokenOwner.moderator)
         self.assertIsNone(member)
     with self.assertRaises(HTTPError) as cm:
         call_api('http://*****:*****@example.com',
             'pre_verified': True,
             'pre_confirmed': True,
             })
     self.assertEqual(cm.exception.code, 409)
     self.assertEqual(cm.exception.reason,
                      b'Subscription request already pending')
Esempio n. 12
0
 def _confirm(self):
     # There will be two messages in the queue - the confirmation messages,
     # and a reply to Anne notifying her of the status of her command
     # email.  We need to dig the confirmation token out of the Subject
     # header of the latter so that we can confirm the subscription.
     messages = get_queue_messages('virgin', sort_on='subject')
     self.assertEqual(len(messages), 2)
     subject_words = str(messages[1].msg['subject']).split()
     self.assertEqual(subject_words[0], 'confirm')
     token = subject_words[1]
     token, token_owner, rmember = IRegistrar(self._mlist).confirm(token)
     self.assertIsNone(token)
     self.assertEqual(token_owner, TokenOwner.no_one)
     # Now, make sure that Anne is a member of the list and is receiving
     # digest deliveries.
     members = getUtility(ISubscriptionService).find_members(
         '*****@*****.**')
     self.assertEqual(len(members), 1)
     self.assertEqual(rmember, members[0])
     return rmember
Esempio n. 13
0
class TestUnsubscription(unittest.TestCase):
    """Test unsubscription requests."""

    layer = SMTPLayer

    def setUp(self):
        self._mlist = create_list('*****@*****.**')
        self._registrar = IRegistrar(self._mlist)

    def test_unsubscribe_defer(self):
        # When unsubscriptions must be approved by the moderator, but the
        # moderator defers this decision.
        anne = getUtility(IUserManager).create_address(
            '*****@*****.**', 'Anne Person')
        token, token_owner, member = self._registrar.register(
            anne, pre_verified=True, pre_confirmed=True, pre_approved=True)
        self.assertIsNone(token)
        self.assertEqual(member.address.email, '*****@*****.**')
        # Now hold and handle an unsubscription request.
        token = hold_unsubscription(self._mlist, '*****@*****.**')
        handle_unsubscription(self._mlist, token, Action.defer)
Esempio n. 14
0
class TestUnsubscription(unittest.TestCase):
    """Test unsubscription requests."""

    layer = SMTPLayer

    def setUp(self):
        self._mlist = create_list('*****@*****.**')
        self._registrar = IRegistrar(self._mlist)

    def test_unsubscribe_defer(self):
        # When unsubscriptions must be approved by the moderator, but the
        # moderator defers this decision.
        anne = getUtility(IUserManager).create_address('*****@*****.**',
                                                       'Anne Person')
        token, token_owner, member = self._registrar.register(
            anne, pre_verified=True, pre_confirmed=True, pre_approved=True)
        self.assertIsNone(token)
        self.assertEqual(member.address.email, '*****@*****.**')
        # Now hold and handle an unsubscription request.
        token = hold_unsubscription(self._mlist, '*****@*****.**')
        handle_unsubscription(self._mlist, token, Action.defer)
Esempio n. 15
0
 def process(self, mlist, msg, msgdata, arguments, results):
     """See `IEmailCommand`."""
     # Parse the arguments.
     delivery_mode = self._parse_arguments(arguments, results)
     if delivery_mode is ContinueProcessing.no:
         return ContinueProcessing.no
     display_name, email = parseaddr(msg['from'])
     # Address could be None or the empty string.
     if not email:
         email = msg.sender
     if not email:
         print(_('$self.name: No valid address found to subscribe'),
               file=results)
         return ContinueProcessing.no
     if isinstance(email, bytes):
         email = email.decode('ascii')
     # Have we already seen one join request from this user during the
     # processing of this email?
     joins = getattr(results, 'joins', set())
     if email in joins:
         # Do not register this join.
         return ContinueProcessing.yes
     joins.add(email)
     results.joins = joins
     person = formataddr((display_name, email))  # noqa
     # Is this person already a member of the list?  Search for all
     # matching memberships.
     members = getUtility(ISubscriptionService).find_members(
         email, mlist.list_id, MemberRole.member)
     if len(members) > 0:
         print(_('$person is already a member'), file=results)
         return ContinueProcessing.yes
     subscriber = match_subscriber(email, display_name)
     IRegistrar(mlist).register(subscriber)
     print(_('Confirmation email sent to $person'), file=results)
     return ContinueProcessing.yes
Esempio n. 16
0
 def __init__(self, mlist, token):
     super().__init__()
     self._mlist = mlist
     self._registrar = IRegistrar(self._mlist)
     self._token = token
Esempio n. 17
0
class TestSubscriptionModeration(unittest.TestCase):
    layer = RESTLayer
    maxDiff = None

    def setUp(self):
        with transaction():
            self._mlist = create_list("*****@*****.**")
            self._registrar = IRegistrar(self._mlist)
            manager = getUtility(IUserManager)
            self._anne = manager.create_address("*****@*****.**", "Anne Person")
            self._bart = manager.make_user("*****@*****.**", "Bart Person")
            preferred = list(self._bart.addresses)[0]
            preferred.verified_on = now()
            self._bart.preferred_address = preferred

    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, b"Cannot convert parameters: action")

    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)
        content, response = call_api("http://*****:*****@example.com/requests")
        self.assertEqual(response.status, 200)
        self.assertEqual(content["total_size"], 2)
        tokens = set(json["token"] for json in content["entries"])
        self.assertEqual(tokens, {token_1, token_2})
        emails = set(json["email"] for json in content["entries"])
        self.assertEqual(emails, {"*****@*****.**", "*****@*****.**"})

    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/{}"
        content, response = call_api(url.format(token))
        self.assertEqual(response.status, 200)
        self.assertEqual(content["token"], token)
        self.assertEqual(content["token_owner"], token_owner.name)
        self.assertEqual(content["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/{}"
        content, response = call_api(url.format(token), dict(action="accept"))
        self.assertEqual(response.status, 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_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="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.
        content, response = call_api("http://*****:*****@example.com/requests")
        self.assertEqual(content["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.
        content, response = call_api("http://*****:*****@example.com/requests")
        self.assertEqual(content["total_size"], 1)
        json = content["entries"][0]
        self.assertEqual(json["token_owner"], "moderator")
        self.assertEqual(json["email"], "*****@*****.**")
        # The moderator approves the request.
        url = "http://*****:*****@example.com/requests/{}"
        content, response = call_api(url.format(token), {"action": "accept"})
        self.assertEqual(response.status, 204)
        # And now the request queue is empty.
        content, response = call_api("http://*****:*****@example.com/requests")
        self.assertEqual(content["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/{}"
        content, response = call_api(url.format(token), dict(action="discard"))
        self.assertEqual(response.status, 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/{}"
        content, response = call_api(url.format(token), dict(action="defer"))
        self.assertEqual(response.status, 204)
        # Anne is not a member.
        self.assertIsNone(self._mlist.members.get_member("*****@*****.**"))
        # The request URL still exists.
        content, response = call_api(url.format(token), dict(action="defer"))
        self.assertEqual(response.status, 204)
        # And now we can accept it.
        content, response = call_api(url.format(token), dict(action="accept"))
        self.assertEqual(response.status, 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/{}"
        content, response = call_api(url.format(token), dict(action="reject"))
        self.assertEqual(response.status, 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)
Esempio n. 18
0
 def on_post(self, request, response):
     """Create a new member."""
     try:
         validator = Validator(
             list_id=str,
             subscriber=subscriber_validator(self.api),
             display_name=str,
             delivery_mode=enum_validator(DeliveryMode),
             role=enum_validator(MemberRole),
             pre_verified=bool,
             pre_confirmed=bool,
             pre_approved=bool,
             _optional=('delivery_mode', 'display_name', 'role',
                        'pre_verified', 'pre_confirmed', 'pre_approved'))
         arguments = validator(request)
     except ValueError as error:
         bad_request(response, str(error))
         return
     # Dig the mailing list out of the arguments.
     list_id = arguments.pop('list_id')
     mlist = getUtility(IListManager).get_by_list_id(list_id)
     if mlist is None:
         bad_request(response, b'No such list')
         return
     # Figure out what kind of subscriber is being registered.  Either it's
     # a user via their preferred email address or it's an explicit address.
     # If it's a UUID, then it must be associated with an existing user.
     subscriber = arguments.pop('subscriber')
     user_manager = getUtility(IUserManager)
     # We use the display name if there is one.
     display_name = arguments.pop('display_name', '')
     if isinstance(subscriber, UUID):
         user = user_manager.get_user_by_id(subscriber)
         if user is None:
             bad_request(response, b'No such user')
             return
         subscriber = user
     else:
         # This must be an email address.  See if there's an existing
         # address object associated with this email.
         address = user_manager.get_address(subscriber)
         if address is None:
             # Create a new address, which of course will not be validated.
             address = user_manager.create_address(
                 subscriber, display_name)
         subscriber = address
     # What role are we subscribing?  Regular members go through the
     # subscription policy workflow while owners, moderators, and
     # nonmembers go through the legacy API for now.
     role = arguments.pop('role', MemberRole.member)
     if role is MemberRole.member:
         # Get the pre_ flags for the subscription workflow.
         pre_verified = arguments.pop('pre_verified', False)
         pre_confirmed = arguments.pop('pre_confirmed', False)
         pre_approved = arguments.pop('pre_approved', False)
         # Now we can run the registration process until either the
         # subscriber is subscribed, or the workflow is paused for
         # verification, confirmation, or approval.
         registrar = IRegistrar(mlist)
         try:
             token, token_owner, member = registrar.register(
                 subscriber,
                 pre_verified=pre_verified,
                 pre_confirmed=pre_confirmed,
                 pre_approved=pre_approved)
         except AlreadySubscribedError:
             conflict(response, b'Member already subscribed')
             return
         except MissingPreferredAddressError:
             bad_request(response, b'User has no preferred address')
             return
         except MembershipIsBannedError:
             bad_request(response, b'Membership is banned')
             return
         except SubscriptionPendingError:
             conflict(response, b'Subscription request already pending')
             return
         if token is None:
             assert token_owner is TokenOwner.no_one, token_owner
             # The subscription completed.  Let's get the resulting member
             # and return the location to the new member.  Member ids are
             # UUIDs and need to be converted to URLs because JSON doesn't
             # directly support UUIDs.
             member_id = self.api.from_uuid(member.member_id)
             location = self.api.path_to('members/{}'.format(member_id))
             created(response, location)
             return
         # The member could not be directly subscribed because there are
         # some out-of-band steps that need to be completed.  E.g. the user
         # must confirm their subscription or the moderator must approve
         # it.  In this case, an HTTP 202 Accepted is exactly the code that
         # we should use, and we'll return both the confirmation token and
         # the "token owner" so the client knows who should confirm it.
         assert token is not None, token
         assert token_owner is not TokenOwner.no_one, token_owner
         assert member is None, member
         content = dict(token=token, token_owner=token_owner.name)
         accepted(response, etag(content))
         return
     # 2015-04-15 BAW: We're subscribing some role other than a regular
     # member.  Use the legacy API for this for now.
     assert role in (MemberRole.owner,
                     MemberRole.moderator,
                     MemberRole.nonmember)
     # 2015-04-15 BAW: We're limited to using an email address with this
     # legacy API, so if the subscriber is a user, the user must have a
     # preferred address, which we'll use, even though it will subscribe
     # the explicit address.  It is an error if the user does not have a
     # preferred address.
     #
     # If the subscriber is an address object, just use that.
     if IUser.providedBy(subscriber):
         if subscriber.preferred_address is None:
             bad_request(response, b'User without preferred address')
             return
         email = subscriber.preferred_address.email
     else:
         assert IAddress.providedBy(subscriber)
         email = subscriber.email
     delivery_mode = arguments.pop('delivery_mode', DeliveryMode.regular)
     record = RequestRecord(email, display_name, delivery_mode)
     try:
         member = add_member(mlist, record, role)
     except MembershipIsBannedError:
         bad_request(response, b'Membership is banned')
         return
     except AlreadySubscribedError:
         bad_request(response,
                     '{} is already an {} of {}'.format(
                         email, role.name, mlist.fqdn_listname))
         return
     # The subscription completed.  Let's get the resulting member
     # and return the location to the new member.  Member ids are
     # UUIDs and need to be converted to URLs because JSON doesn't
     # directly support UUIDs.
     member_id = self.api.from_uuid(member.member_id)
     location = self.api.path_to('members/{}'.format(member_id))
     created(response, location)
Esempio n. 19
0
 def setUp(self):
     self._mlist = create_list('*****@*****.**')
     self._registrar = IRegistrar(self._mlist)
     self._pendings = getUtility(IPendings)
     self._anne = getUtility(IUserManager).create_address(
         '*****@*****.**')
Esempio n. 20
0
class TestRegistrar(unittest.TestCase):
    """Test registration."""

    layer = ConfigLayer

    def setUp(self):
        self._mlist = create_list('*****@*****.**')
        self._registrar = IRegistrar(self._mlist)
        self._pendings = getUtility(IPendings)
        self._anne = getUtility(IUserManager).create_address(
            '*****@*****.**')

    def test_initial_conditions(self):
        # Registering a subscription request provides a unique token associated
        # with a pendable, and the owner of the token.
        self.assertEqual(self._pendings.count, 0)
        token, token_owner, member = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.subscriber)
        self.assertIsNone(member)
        self.assertEqual(self._pendings.count, 1)
        record = self._pendings.confirm(token, expunge=False)
        self.assertEqual(record['list_id'], self._mlist.list_id)
        self.assertEqual(record['email'], '*****@*****.**')

    def test_subscribe(self):
        # Registering a subscription request where no confirmation or
        # moderation steps are needed, leaves us with no token or owner, since
        # there's nothing more to do.
        self._mlist.subscription_policy = SubscriptionPolicy.open
        self._anne.verified_on = now()
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNone(token)
        self.assertEqual(token_owner, TokenOwner.no_one)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertEqual(rmember, member)
        self.assertEqual(member.address, self._anne)
        # There's nothing to confirm.
        record = self._pendings.confirm(token, expunge=False)
        self.assertIsNone(record)

    def test_no_such_token(self):
        # Given a token which is not in the database, a LookupError is raised.
        self._registrar.register(self._anne)
        self.assertRaises(LookupError, self._registrar.confirm, 'not-a-token')

    def test_confirm_because_verify(self):
        # We have a subscription request which requires the user to confirm
        # (because she does not have a verified address), but not the moderator
        # to approve.  Running the workflow gives us a token.  Confirming the
        # token subscribes the user.
        self._mlist.subscription_policy = SubscriptionPolicy.open
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.subscriber)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Now confirm the subscription.
        token, token_owner, rmember = self._registrar.confirm(token)
        self.assertIsNone(token)
        self.assertEqual(token_owner, TokenOwner.no_one)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertEqual(rmember, member)
        self.assertEqual(member.address, self._anne)

    def test_confirm_because_confirm(self):
        # We have a subscription request which requires the user to confirm
        # (because of list policy), but not the moderator to approve.  Running
        # the workflow gives us a token.  Confirming the token subscribes the
        # user.
        self._mlist.subscription_policy = SubscriptionPolicy.confirm
        self._anne.verified_on = now()
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.subscriber)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Now confirm the subscription.
        token, token_owner, rmember = self._registrar.confirm(token)
        self.assertIsNone(token)
        self.assertEqual(token_owner, TokenOwner.no_one)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertEqual(rmember, member)
        self.assertEqual(member.address, self._anne)

    def test_confirm_because_moderation(self):
        # We have a subscription request which requires the moderator to
        # approve.  Running the workflow gives us a token.  Confirming the
        # token subscribes the user.
        self._mlist.subscription_policy = SubscriptionPolicy.moderate
        self._anne.verified_on = now()
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.moderator)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Now confirm the subscription.
        token, token_owner, rmember = self._registrar.confirm(token)
        self.assertIsNone(token)
        self.assertEqual(token_owner, TokenOwner.no_one)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertEqual(rmember, member)
        self.assertEqual(member.address, self._anne)

    def test_confirm_because_confirm_then_moderation(self):
        # We have a subscription request which requires the user to confirm
        # (because she does not have a verified address) and the moderator to
        # approve.  Running the workflow gives us a token.  Confirming the
        # token runs the workflow a little farther, but still gives us a
        # token.  Confirming again subscribes the user.
        self._mlist.subscription_policy = (
            SubscriptionPolicy.confirm_then_moderate)
        self._anne.verified_on = now()
        # Runs until subscription confirmation.
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.subscriber)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Now confirm the subscription, and wait for the moderator to approve
        # the subscription.  She is still not subscribed.
        new_token, token_owner, rmember = self._registrar.confirm(token)
        # The new token, used for the moderator to approve the message, is not
        # the same as the old token.
        self.assertNotEqual(new_token, token)
        self.assertIsNotNone(new_token)
        self.assertEqual(token_owner, TokenOwner.moderator)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Confirm once more, this time as the moderator approving the
        # subscription.  Now she's a member.
        token, token_owner, rmember = self._registrar.confirm(new_token)
        self.assertIsNone(token)
        self.assertEqual(token_owner, TokenOwner.no_one)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertEqual(rmember, member)
        self.assertEqual(member.address, self._anne)

    def test_confirm_then_moderate_with_different_tokens(self):
        # Ensure that the confirmation token the user sees when they have to
        # confirm their subscription is different than the token the moderator
        # sees when they approve the subscription.  This prevents the user
        # from using a replay attack to subvert moderator approval.
        self._mlist.subscription_policy = (
            SubscriptionPolicy.confirm_then_moderate)
        self._anne.verified_on = now()
        # Runs until subscription confirmation.
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.subscriber)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Now confirm the subscription, and wait for the moderator to approve
        # the subscription.  She is still not subscribed.
        new_token, token_owner, rmember = self._registrar.confirm(token)
        # The status is not true because the user has not yet been subscribed
        # to the mailing list.
        self.assertIsNotNone(new_token)
        self.assertEqual(token_owner, TokenOwner.moderator)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # The new token is different than the old token.
        self.assertNotEqual(token, new_token)
        # Trying to confirm with the old token does not work.
        self.assertRaises(LookupError, self._registrar.confirm, token)
        # Confirm once more, this time with the new token, as the moderator
        # approving the subscription.  Now she's a member.
        done_token, token_owner, rmember = self._registrar.confirm(new_token)
        # The token is None, signifying that the member has been subscribed.
        self.assertIsNone(done_token)
        self.assertEqual(token_owner, TokenOwner.no_one)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertEqual(rmember, member)
        self.assertEqual(member.address, self._anne)

    def test_discard_waiting_for_confirmation(self):
        # While waiting for a user to confirm their subscription, we discard
        # the workflow.
        self._mlist.subscription_policy = SubscriptionPolicy.confirm
        self._anne.verified_on = now()
        # Runs until subscription confirmation.
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.subscriber)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Now discard the subscription request.
        self._registrar.discard(token)
        # Trying to confirm the token now results in an exception.
        self.assertRaises(LookupError, self._registrar.confirm, token)

    def test_admin_notify_mchanges(self):
        # When a user gets subscribed via the subscription policy workflow,
        # the list administrators get an email notification.
        self._mlist.subscription_policy = SubscriptionPolicy.open
        self._mlist.admin_notify_mchanges = True
        self._mlist.send_welcome_message = False
        token, token_owner, member = self._registrar.register(
            self._anne, pre_verified=True)
        # Anne is now a member.
        self.assertEqual(member.address.email, '*****@*****.**')
        # And there's a notification email waiting for Bart.
        items = get_queue_messages('virgin', expected_count=1)
        message = items[0].msg
        self.assertEqual(message['To'], '*****@*****.**')
        self.assertEqual(message['Subject'], 'Ant subscription notification')
        self.assertEqual(message.get_payload(), """\
[email protected] has been successfully subscribed to Ant.""")

    def test_no_admin_notify_mchanges(self):
        # Even when a user gets subscribed via the subscription policy
        # workflow, the list administrators won't get an email notification if
        # they don't want one.
        self._mlist.subscription_policy = SubscriptionPolicy.open
        self._mlist.admin_notify_mchanges = False
        self._mlist.send_welcome_message = False
        # Bart is an administrator of the mailing list.
        bart = getUtility(IUserManager).create_address(
            '*****@*****.**', 'Bart Person')
        self._mlist.subscribe(bart, MemberRole.owner)
        token, token_owner, member = self._registrar.register(
            self._anne, pre_verified=True)
        # Anne is now a member.
        self.assertEqual(member.address.email, '*****@*****.**')
        # There's no notification email waiting for Bart.
        get_queue_messages('virgin', expected_count=0)
Esempio n. 21
0
 def setUp(self):
     self._mlist = create_list('*****@*****.**')
     self._registrar = IRegistrar(self._mlist)
Esempio n. 22
0
 def __init__(self, mlist, token):
     super().__init__()
     self._mlist = mlist
     self._registrar = IRegistrar(self._mlist)
     self._token = token
Esempio n. 23
0
class TestSubscriptionModeration(unittest.TestCase):
    layer = RESTLayer
    maxDiff = None

    def setUp(self):
        with transaction():
            self._mlist = create_list('*****@*****.**')
            self._registrar = IRegistrar(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,
                         b'Cannot convert parameters: action')

    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)
        content, response = call_api(
            'http://*****:*****@example.com/requests')
        self.assertEqual(response.status, 200)
        self.assertEqual(content['total_size'], 2)
        tokens = set(json['token'] for json in content['entries'])
        self.assertEqual(tokens, {token_1, token_2})
        emails = set(json['email'] for json in content['entries'])
        self.assertEqual(emails, {'*****@*****.**', '*****@*****.**'})

    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/{}'
        content, response = call_api(url.format(token))
        self.assertEqual(response.status, 200)
        self.assertEqual(content['token'], token)
        self.assertEqual(content['token_owner'], token_owner.name)
        self.assertEqual(content['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/{}'
        content, response = call_api(url.format(token),
                                     dict(action='accept', ))
        self.assertEqual(response.status, 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.
        content, response = call_api(
            'http://*****:*****@example.com/requests')
        self.assertEqual(content['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.
        content, response = call_api(
            'http://*****:*****@example.com/requests')
        self.assertEqual(content['total_size'], 1)
        json = content['entries'][0]
        self.assertEqual(json['token_owner'], 'moderator')
        self.assertEqual(json['email'], '*****@*****.**')
        # The moderator approves the request.
        url = 'http://*****:*****@example.com/requests/{}'
        content, response = call_api(url.format(token), {'action': 'accept'})
        self.assertEqual(response.status, 204)
        # And now the request queue is empty.
        content, response = call_api(
            'http://*****:*****@example.com/requests')
        self.assertEqual(content['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/{}'
        content, response = call_api(url.format(token),
                                     dict(action='discard', ))
        self.assertEqual(response.status, 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/{}'
        content, response = call_api(url.format(token), dict(action='defer', ))
        self.assertEqual(response.status, 204)
        # Anne is not a member.
        self.assertIsNone(self._mlist.members.get_member('*****@*****.**'))
        # The request URL still exists.
        content, response = call_api(url.format(token), dict(action='defer', ))
        self.assertEqual(response.status, 204)
        # And now we can accept it.
        content, response = call_api(url.format(token),
                                     dict(action='accept', ))
        self.assertEqual(response.status, 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/{}'
        content, response = call_api(url.format(token),
                                     dict(action='reject', ))
        self.assertEqual(response.status, 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_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/{}'
        content, response = call_api(url.format(token), dict(action='hold', ))
        self.assertEqual(response.status, 204)
        # Anne is not a member.
        self.assertIsNone(self._mlist.members.get_member('*****@*****.**'))
        # The request URL still exists.
        content, response = call_api(url.format(token), dict(action='defer', ))
        self.assertEqual(response.status, 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, b'Membership is banned')
Esempio n. 24
0
    def test_confirm_then_moderate_workflow(self):
        # Issue #114 describes a problem when confirming the moderation email.
        self._mlist.subscription_policy = (
            SubscriptionPolicy.confirm_then_moderate)
        bart = getUtility(IUserManager).create_address('*****@*****.**',
                                                       'Bart Person')
        # Clear any previously queued confirmation messages.
        get_queue_messages('virgin')
        self._token, token_owner, member = IRegistrar(
            self._mlist).register(bart)
        # There should now be one email message in the virgin queue, i.e. the
        # confirmation message sent to Bart.
        items = get_queue_messages('virgin', expected_count=1)
        msg = items[0].msg
        # Confirmations come first, so this one goes to the subscriber.
        self.assertEqual(msg['to'], '*****@*****.**')
        confirm, token = str(msg['subject']).split()
        self.assertEqual(confirm, 'confirm')
        self.assertEqual(token, self._token)
        # Craft a confirmation response with the expected tokens.
        user_response = Message()
        user_response['From'] = '*****@*****.**'
        user_response['To'] = 'test-confirm+{}@example.com'.format(token)
        user_response['Subject'] = 'Re: confirm {}'.format(token)
        user_response.set_payload('')
        # Process the message through the command runner.
        config.switchboards['command'].enqueue(user_response,
                                               listid='test.example.com')
        make_testable_runner(CommandRunner, 'command').run()
        # There are now two messages in the virgin queue.  One is going to the
        # subscriber containing the results of their confirmation message, and
        # the other is to the moderators informing them that they need to
        # handle the moderation queue.
        items = get_queue_messages('virgin', expected_count=2)
        if items[0].msg['to'] == '*****@*****.**':
            results = items[0].msg
            moderator_msg = items[1].msg
        else:
            results = items[1].msg
            moderator_msg = items[0].msg
        # Check the moderator message first.
        self.assertEqual(moderator_msg['to'], '*****@*****.**')
        self.assertEqual(
            moderator_msg['subject'],
            'New subscription request to Test from [email protected]')
        lines = moderator_msg.get_payload().splitlines()
        self.assertEqual(lines[-2].strip(),
                         'For:  Bart Person <*****@*****.**>')
        self.assertEqual(lines[-1].strip(), 'List: [email protected]')
        # Now check the results message.
        self.assertEqual(str(results['subject']),
                         'The results of your email commands')
        self.assertMultiLineEqual(
            results.get_payload(), """\
The results of your email command are provided below.

- Original message details:
From: [email protected]
Subject: Re: confirm {}
Date: n/a
Message-ID: n/a

- Results:
Confirmed

- Done.
""".format(token))
Esempio n. 25
0
class TestSubscriptionModeration(unittest.TestCase):
    layer = RESTLayer
    maxDiff = None

    def setUp(self):
        with transaction():
            self._mlist = create_list('*****@*****.**')
            self._registrar = IRegistrar(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,
                         b'Cannot convert parameters: action')

    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)
        content, response = call_api(
            'http://*****:*****@example.com/requests')
        self.assertEqual(response.status, 200)
        self.assertEqual(content['total_size'], 2)
        tokens = set(json['token'] for json in content['entries'])
        self.assertEqual(tokens, {token_1, token_2})
        emails = set(json['email'] for json in content['entries'])
        self.assertEqual(emails, {'*****@*****.**', '*****@*****.**'})

    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/{}'
        content, response = call_api(url.format(token))
        self.assertEqual(response.status, 200)
        self.assertEqual(content['token'], token)
        self.assertEqual(content['token_owner'], token_owner.name)
        self.assertEqual(content['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/{}'
        content, response = call_api(url.format(token), dict(
            action='accept',
            ))
        self.assertEqual(response.status, 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.
        content, response = call_api(
            'http://*****:*****@example.com/requests')
        self.assertEqual(content['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.
        content, response = call_api(
            'http://*****:*****@example.com/requests')
        self.assertEqual(content['total_size'], 1)
        json = content['entries'][0]
        self.assertEqual(json['token_owner'], 'moderator')
        self.assertEqual(json['email'], '*****@*****.**')
        # The moderator approves the request.
        url = 'http://*****:*****@example.com/requests/{}'
        content, response = call_api(url.format(token), {'action': 'accept'})
        self.assertEqual(response.status, 204)
        # And now the request queue is empty.
        content, response = call_api(
            'http://*****:*****@example.com/requests')
        self.assertEqual(content['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/{}'
        content, response = call_api(url.format(token), dict(
            action='discard',
            ))
        self.assertEqual(response.status, 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/{}'
        content, response = call_api(url.format(token), dict(
            action='defer',
            ))
        self.assertEqual(response.status, 204)
        # Anne is not a member.
        self.assertIsNone(self._mlist.members.get_member('*****@*****.**'))
        # The request URL still exists.
        content, response = call_api(url.format(token), dict(
            action='defer',
            ))
        self.assertEqual(response.status, 204)
        # And now we can accept it.
        content, response = call_api(url.format(token), dict(
            action='accept',
            ))
        self.assertEqual(response.status, 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/{}'
        content, response = call_api(url.format(token), dict(
            action='reject',
            ))
        self.assertEqual(response.status, 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_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/{}'
        content, response = call_api(url.format(token), dict(
            action='hold',
            ))
        self.assertEqual(response.status, 204)
        # Anne is not a member.
        self.assertIsNone(self._mlist.members.get_member('*****@*****.**'))
        # The request URL still exists.
        content, response = call_api(url.format(token), dict(
                action='defer',
                ))
        self.assertEqual(response.status, 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, b'Membership is banned')
Esempio n. 26
0
 def setUp(self):
     self._mlist = create_list('*****@*****.**')
     self._registrar = IRegistrar(self._mlist)
Esempio n. 27
0
class TestRegistrar(unittest.TestCase):
    """Test registration."""

    layer = ConfigLayer

    def setUp(self):
        self._mlist = create_list('*****@*****.**')
        self._registrar = IRegistrar(self._mlist)
        self._pendings = getUtility(IPendings)
        self._anne = getUtility(IUserManager).create_address(
            '*****@*****.**')

    def test_initial_conditions(self):
        # Registering a subscription request provides a unique token associated
        # with a pendable, and the owner of the token.
        self.assertEqual(self._pendings.count, 0)
        token, token_owner, member = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.subscriber)
        self.assertIsNone(member)
        self.assertEqual(self._pendings.count, 1)
        record = self._pendings.confirm(token, expunge=False)
        self.assertEqual(record['list_id'], self._mlist.list_id)
        self.assertEqual(record['email'], '*****@*****.**')

    def test_subscribe(self):
        # Registering a subscription request where no confirmation or
        # moderation steps are needed, leaves us with no token or owner, since
        # there's nothing more to do.
        self._mlist.subscription_policy = SubscriptionPolicy.open
        self._anne.verified_on = now()
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNone(token)
        self.assertEqual(token_owner, TokenOwner.no_one)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertEqual(rmember, member)
        self.assertEqual(member.address, self._anne)
        # There's nothing to confirm.
        record = self._pendings.confirm(token, expunge=False)
        self.assertIsNone(record)

    def test_no_such_token(self):
        # Given a token which is not in the database, a LookupError is raised.
        self._registrar.register(self._anne)
        self.assertRaises(LookupError, self._registrar.confirm, 'not-a-token')

    def test_confirm_because_verify(self):
        # We have a subscription request which requires the user to confirm
        # (because she does not have a verified address), but not the moderator
        # to approve.  Running the workflow gives us a token.  Confirming the
        # token subscribes the user.
        self._mlist.subscription_policy = SubscriptionPolicy.open
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.subscriber)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Now confirm the subscription.
        token, token_owner, rmember = self._registrar.confirm(token)
        self.assertIsNone(token)
        self.assertEqual(token_owner, TokenOwner.no_one)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertEqual(rmember, member)
        self.assertEqual(member.address, self._anne)

    def test_confirm_because_confirm(self):
        # We have a subscription request which requires the user to confirm
        # (because of list policy), but not the moderator to approve.  Running
        # the workflow gives us a token.  Confirming the token subscribes the
        # user.
        self._mlist.subscription_policy = SubscriptionPolicy.confirm
        self._anne.verified_on = now()
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.subscriber)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Now confirm the subscription.
        token, token_owner, rmember = self._registrar.confirm(token)
        self.assertIsNone(token)
        self.assertEqual(token_owner, TokenOwner.no_one)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertEqual(rmember, member)
        self.assertEqual(member.address, self._anne)

    def test_confirm_because_moderation(self):
        # We have a subscription request which requires the moderator to
        # approve.  Running the workflow gives us a token.  Confirming the
        # token subscribes the user.
        self._mlist.subscription_policy = SubscriptionPolicy.moderate
        self._anne.verified_on = now()
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.moderator)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Now confirm the subscription.
        token, token_owner, rmember = self._registrar.confirm(token)
        self.assertIsNone(token)
        self.assertEqual(token_owner, TokenOwner.no_one)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertEqual(rmember, member)
        self.assertEqual(member.address, self._anne)

    def test_confirm_because_confirm_then_moderation(self):
        # We have a subscription request which requires the user to confirm
        # (because she does not have a verified address) and the moderator to
        # approve.  Running the workflow gives us a token.  Confirming the
        # token runs the workflow a little farther, but still gives us a
        # token.  Confirming again subscribes the user.
        self._mlist.subscription_policy = \
          SubscriptionPolicy.confirm_then_moderate
        self._anne.verified_on = now()
        # Runs until subscription confirmation.
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.subscriber)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Now confirm the subscription, and wait for the moderator to approve
        # the subscription.  She is still not subscribed.
        new_token, token_owner, rmember = self._registrar.confirm(token)
        # The new token, used for the moderator to approve the message, is not
        # the same as the old token.
        self.assertNotEqual(new_token, token)
        self.assertIsNotNone(new_token)
        self.assertEqual(token_owner, TokenOwner.moderator)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Confirm once more, this time as the moderator approving the
        # subscription.  Now she's a member.
        token, token_owner, rmember = self._registrar.confirm(new_token)
        self.assertIsNone(token)
        self.assertEqual(token_owner, TokenOwner.no_one)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertEqual(rmember, member)
        self.assertEqual(member.address, self._anne)

    def test_confirm_then_moderate_with_different_tokens(self):
        # Ensure that the confirmation token the user sees when they have to
        # confirm their subscription is different than the token the moderator
        # sees when they approve the subscription.  This prevents the user
        # from using a replay attack to subvert moderator approval.
        self._mlist.subscription_policy = \
          SubscriptionPolicy.confirm_then_moderate
        self._anne.verified_on = now()
        # Runs until subscription confirmation.
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.subscriber)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Now confirm the subscription, and wait for the moderator to approve
        # the subscription.  She is still not subscribed.
        new_token, token_owner, rmember = self._registrar.confirm(token)
        # The status is not true because the user has not yet been subscribed
        # to the mailing list.
        self.assertIsNotNone(new_token)
        self.assertEqual(token_owner, TokenOwner.moderator)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # The new token is different than the old token.
        self.assertNotEqual(token, new_token)
        # Trying to confirm with the old token does not work.
        self.assertRaises(LookupError, self._registrar.confirm, token)
        # Confirm once more, this time with the new token, as the moderator
        # approving the subscription.  Now she's a member.
        done_token, token_owner, rmember = self._registrar.confirm(new_token)
        # The token is None, signifying that the member has been subscribed.
        self.assertIsNone(done_token)
        self.assertEqual(token_owner, TokenOwner.no_one)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertEqual(rmember, member)
        self.assertEqual(member.address, self._anne)

    def test_discard_waiting_for_confirmation(self):
        # While waiting for a user to confirm their subscription, we discard
        # the workflow.
        self._mlist.subscription_policy = SubscriptionPolicy.confirm
        self._anne.verified_on = now()
        # Runs until subscription confirmation.
        token, token_owner, rmember = self._registrar.register(self._anne)
        self.assertIsNotNone(token)
        self.assertEqual(token_owner, TokenOwner.subscriber)
        self.assertIsNone(rmember)
        member = self._mlist.regular_members.get_member('*****@*****.**')
        self.assertIsNone(member)
        # Now discard the subscription request.
        self._registrar.discard(token)
        # Trying to confirm the token now results in an exception.
        self.assertRaises(LookupError, self._registrar.confirm, token)

    def test_admin_notify_mchanges(self):
        # When a user gets subscribed via the subscription policy workflow,
        # the list administrators get an email notification.
        self._mlist.subscription_policy = SubscriptionPolicy.open
        self._mlist.admin_notify_mchanges = True
        self._mlist.send_welcome_message = False
        token, token_owner, member = self._registrar.register(
            self._anne, pre_verified=True)
        # Anne is now a member.
        self.assertEqual(member.address.email, '*****@*****.**')
        # And there's a notification email waiting for Bart.
        items = get_queue_messages('virgin')
        self.assertEqual(len(items), 1)
        message = items[0].msg
        self.assertEqual(message['To'], '*****@*****.**')
        self.assertEqual(message['Subject'], 'Ant subscription notification')
        self.assertEqual(message.get_payload(), """\
[email protected] has been successfully subscribed to Ant.""")

    def test_no_admin_notify_mchanges(self):
        # Even when a user gets subscribed via the subscription policy
        # workflow, the list administrators won't get an email notification if
        # they don't want one.
        self._mlist.subscription_policy = SubscriptionPolicy.open
        self._mlist.admin_notify_mchanges = False
        self._mlist.send_welcome_message = False
        # Bart is an administrator of the mailing list.
        bart = getUtility(IUserManager).create_address(
            '*****@*****.**', 'Bart Person')
        self._mlist.subscribe(bart, MemberRole.owner)
        token, token_owner, member = self._registrar.register(
            self._anne, pre_verified=True)
        # Anne is now a member.
        self.assertEqual(member.address.email, '*****@*****.**')
        # There's no notification email waiting for Bart.
        items = get_queue_messages('virgin')
        self.assertEqual(len(items), 0)
Esempio n. 28
0
 def setUp(self):
     self._mlist = create_list('*****@*****.**')
     self._registrar = IRegistrar(self._mlist)
     self._pendings = getUtility(IPendings)
     self._anne = getUtility(IUserManager).create_address(
         '*****@*****.**')
Esempio n. 29
0
 def on_post(self, request, response):
     """Create a new member."""
     try:
         validator = Validator(list_id=str,
                               subscriber=subscriber_validator,
                               display_name=str,
                               delivery_mode=enum_validator(DeliveryMode),
                               role=enum_validator(MemberRole),
                               pre_verified=bool,
                               pre_confirmed=bool,
                               pre_approved=bool,
                               _optional=('delivery_mode', 'display_name',
                                          'role', 'pre_verified',
                                          'pre_confirmed', 'pre_approved'))
         arguments = validator(request)
     except ValueError as error:
         bad_request(response, str(error))
         return
     # Dig the mailing list out of the arguments.
     list_id = arguments.pop('list_id')
     mlist = getUtility(IListManager).get_by_list_id(list_id)
     if mlist is None:
         bad_request(response, b'No such list')
         return
     # Figure out what kind of subscriber is being registered.  Either it's
     # a user via their preferred email address or it's an explicit address.
     # If it's a UUID, then it must be associated with an existing user.
     subscriber = arguments.pop('subscriber')
     user_manager = getUtility(IUserManager)
     # We use the display name if there is one.
     display_name = arguments.pop('display_name', '')
     if isinstance(subscriber, UUID):
         user = user_manager.get_user_by_id(subscriber)
         if user is None:
             bad_request(response, b'No such user')
             return
         subscriber = user
     else:
         # This must be an email address.  See if there's an existing
         # address object associated with this email.
         address = user_manager.get_address(subscriber)
         if address is None:
             # Create a new address, which of course will not be validated.
             address = user_manager.create_address(subscriber, display_name)
         subscriber = address
     # What role are we subscribing?  Regular members go through the
     # subscription policy workflow while owners, moderators, and
     # nonmembers go through the legacy API for now.
     role = arguments.pop('role', MemberRole.member)
     if role is MemberRole.member:
         # Get the pre_ flags for the subscription workflow.
         pre_verified = arguments.pop('pre_verified', False)
         pre_confirmed = arguments.pop('pre_confirmed', False)
         pre_approved = arguments.pop('pre_approved', False)
         # Now we can run the registration process until either the
         # subscriber is subscribed, or the workflow is paused for
         # verification, confirmation, or approval.
         registrar = IRegistrar(mlist)
         try:
             token, token_owner, member = registrar.register(
                 subscriber,
                 pre_verified=pre_verified,
                 pre_confirmed=pre_confirmed,
                 pre_approved=pre_approved)
         except AlreadySubscribedError:
             conflict(response, b'Member already subscribed')
             return
         if token is None:
             assert token_owner is TokenOwner.no_one, token_owner
             # The subscription completed.  Let's get the resulting member
             # and return the location to the new member.  Member ids are
             # UUIDs and need to be converted to URLs because JSON doesn't
             # directly support UUIDs.
             member_id = member.member_id.int
             location = self.path_to('members/{0}'.format(member_id))
             created(response, location)
             return
         # The member could not be directly subscribed because there are
         # some out-of-band steps that need to be completed.  E.g. the user
         # must confirm their subscription or the moderator must approve
         # it.  In this case, an HTTP 202 Accepted is exactly the code that
         # we should use, and we'll return both the confirmation token and
         # the "token owner" so the client knows who should confirm it.
         assert token is not None, token
         assert token_owner is not TokenOwner.no_one, token_owner
         assert member is None, member
         content = dict(token=token, token_owner=token_owner.name)
         accepted(response, etag(content))
         return
     # 2015-04-15 BAW: We're subscribing some role other than a regular
     # member.  Use the legacy API for this for now.
     assert role in (MemberRole.owner, MemberRole.moderator,
                     MemberRole.nonmember)
     # 2015-04-15 BAW: We're limited to using an email address with this
     # legacy API, so if the subscriber is a user, the user must have a
     # preferred address, which we'll use, even though it will subscribe
     # the explicit address.  It is an error if the user does not have a
     # preferred address.
     #
     # If the subscriber is an address object, just use that.
     if IUser.providedBy(subscriber):
         if subscriber.preferred_address is None:
             bad_request(response, b'User without preferred address')
             return
         email = subscriber.preferred_address.email
     else:
         assert IAddress.providedBy(subscriber)
         email = subscriber.email
     delivery_mode = arguments.pop('delivery_mode', DeliveryMode.regular)
     record = RequestRecord(email, display_name, delivery_mode)
     try:
         member = add_member(mlist, record, role)
     except InvalidEmailAddressError:
         bad_request(response, b'Invalid email address')
         return
     except MembershipIsBannedError:
         bad_request(response, b'Membership is banned')
         return
     # The subscription completed.  Let's get the resulting member
     # and return the location to the new member.  Member ids are
     # UUIDs and need to be converted to URLs because JSON doesn't
     # directly support UUIDs.
     member_id = member.member_id.int
     location = self.path_to('members/{0}'.format(member_id))
     created(response, location)