diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/app/subscriptions.py | 5 | ||||
| -rw-r--r-- | src/mailman/app/tests/test_subscriptions.py | 5 | ||||
| -rw-r--r-- | src/mailman/rest/docs/post-moderation.rst | 1 | ||||
| -rw-r--r-- | src/mailman/rest/docs/sub-moderation.rst | 8 | ||||
| -rw-r--r-- | src/mailman/rest/sub_moderation.py | 89 | ||||
| -rw-r--r-- | src/mailman/rest/tests/test_moderation.py | 131 |
6 files changed, 147 insertions, 92 deletions
diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py index 46ce549af..8794fb78a 100644 --- a/src/mailman/app/subscriptions.py +++ b/src/mailman/app/subscriptions.py @@ -168,8 +168,9 @@ class SubscriptionWorkflow(Workflow): return pendable = Pendable( list_id=self.mlist.list_id, - address=self.address.email, - hold_date=now().replace(microsecond=0).isoformat(), + email=self.address.email, + display_name=self.address.display_name, + when=now().replace(microsecond=0).isoformat(), token_owner=token_owner.name, ) self.token = getUtility(IPendings).add(pendable, timedelta(days=3650)) diff --git a/src/mailman/app/tests/test_subscriptions.py b/src/mailman/app/tests/test_subscriptions.py index 6b5d88026..2d5a3733b 100644 --- a/src/mailman/app/tests/test_subscriptions.py +++ b/src/mailman/app/tests/test_subscriptions.py @@ -69,8 +69,9 @@ class TestSubscriptionWorkflow(unittest.TestCase): self.assertIsNotNone(workflow.token) pendable = getUtility(IPendings).confirm(workflow.token, expunge=False) self.assertEqual(pendable['list_id'], 'test.example.com') - self.assertEqual(pendable['address'], 'anne@example.com') - self.assertEqual(pendable['hold_date'], '2005-08-01T07:49:23') + self.assertEqual(pendable['email'], 'anne@example.com') + self.assertEqual(pendable['display_name'], '') + self.assertEqual(pendable['when'], '2005-08-01T07:49:23') self.assertEqual(pendable['token_owner'], 'subscriber') def test_user_or_address_required(self): diff --git a/src/mailman/rest/docs/post-moderation.rst b/src/mailman/rest/docs/post-moderation.rst index f082de157..6dd96e71a 100644 --- a/src/mailman/rest/docs/post-moderation.rst +++ b/src/mailman/rest/docs/post-moderation.rst @@ -13,6 +13,7 @@ Held messages can be moderated through the REST API. A mailing list starts with no held messages. >>> ant = create_list('ant@example.com') + >>> transaction.commit() >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/held') http_etag: "..." start: 0 diff --git a/src/mailman/rest/docs/sub-moderation.rst b/src/mailman/rest/docs/sub-moderation.rst index a658ac410..0468d75b5 100644 --- a/src/mailman/rest/docs/sub-moderation.rst +++ b/src/mailman/rest/docs/sub-moderation.rst @@ -42,15 +42,15 @@ The message is being held for moderator approval. The subscription request can be viewed in the REST API. + >>> transaction.commit() >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests') entry 0: - delivery_mode: regular display_name: Anne Person email: anne@example.com http_etag: "..." - language: en - request_id: ... - type: subscription + list_id: ant.example.com + token: ... + token_owner: moderator when: 2005-08-01T07:49:23 http_etag: "..." start: 0 diff --git a/src/mailman/rest/sub_moderation.py b/src/mailman/rest/sub_moderation.py index 7bc6554a6..12bd8ab26 100644 --- a/src/mailman/rest/sub_moderation.py +++ b/src/mailman/rest/sub_moderation.py @@ -38,8 +38,11 @@ class _ModerationBase: def __init__(self): self._pendings = getUtility(IPendings) - def _make_resource(self, token): + def _resource_as_dict(self, token): pendable = self._pendings.confirm(token, expunge=False) + if pendable is None: + # This token isn't in the database. + raise LookupError resource = dict(token=token) resource.update(pendable) return resource @@ -50,28 +53,20 @@ 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: - token, token_owner, member = self._registrar.confirm(self._token) + resource = self._resource_as_dict(self._token) except LookupError: not_found(response) return - try: - request_id = int(self._request_id) - except ValueError: - bad_request(response) - return - resource = self._make_resource(request_id, MEMBERSHIP_CHANGE_REQUESTS) - if resource is None: - not_found(response) - else: - # Remove unnecessary keys. - del resource['key'] - okay(response, etag(resource)) + okay(response, etag(resource)) def on_post(self, request, response): try: @@ -80,32 +75,34 @@ class IndividualRequest(_ModerationBase): except ValueError as error: bad_request(response, str(error)) return - requests = IListRequests(self._mlist) - try: - request_id = int(self._request_id) - except ValueError: - bad_request(response) - return - results = requests.get_request(request_id) - if results is None: - not_found(response) - return - key, data = results - try: - request_type = RequestType[data['_request_type']] - except ValueError: - bad_request(response) - return - if request_type is RequestType.subscription: - handle_subscription(self._mlist, request_id, **arguments) - elif request_type is RequestType.unsubscription: - handle_unsubscription(self._mlist, request_id, **arguments) - else: - bad_request(response) - return - no_content(response) + 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: + # XXX + no_content(response) + class SubscriptionRequests(_ModerationBase, CollectionMixin): """Resource for membership change requests.""" @@ -116,8 +113,20 @@ class SubscriptionRequests(_ModerationBase, CollectionMixin): def _get_collection(self, request): # There's currently no better way to query the pendings database for # all the entries that are associated with subscription holds on this - # mailing list. Brute force for now. - return [token for token, pendable in getUtility(IPendings)] + # mailing list. Brute force iterating over all the pendables. + collection = [] + for token, pendable in getUtility(IPendings): + if 'token_owner' not in pendable: + # This isn't a subscription hold. + continue + list_id = pendable.get('list_id') + if list_id != self._mlist.list_id: + # Either there isn't a list_id field, in which case it can't + # be a subscription hold, or this is a hold for some other + # mailing list. + continue + collection.append(token) + return collection def on_get(self, request, response): """/lists/listname/requests""" diff --git a/src/mailman/rest/tests/test_moderation.py b/src/mailman/rest/tests/test_moderation.py index ab7383251..cc1f67f7e 100644 --- a/src/mailman/rest/tests/test_moderation.py +++ b/src/mailman/rest/tests/test_moderation.py @@ -145,30 +145,51 @@ class TestSubscriptionModeration(unittest.TestCase): def test_list_held_requests(self): # We can view all the held requests. - token_1, token_owner, member = self._registrar.register(self._anne) - # Anne's subscription request got held. - self.assertIsNone(member) - token_2, token_owner, member = self._registrar.register(self._bart) + 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://localhost:9001/3.0/lists/ant@example.com/requests') self.assertEqual(response.status, 200) self.assertEqual(content['total_size'], 2) - import pdb; pdb.set_trace() + 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, {'anne@example.com', 'bart@example.com'}) + + 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://localhost:9001/3.0/lists/ant@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'], 'anne@example.com') def test_accept(self): # POST to the request to accept it. - token, token_owner, member = self._registrar.register(self._anne) + with transaction(): + token, token_owner, member = self._registrar.register(self._anne) # Anne's subscription request got held. self.assertIsNone(member) url = 'http://localhost:9001/3.0/lists/ant@example.com/requests/{}' - with self.assertRaises(HTTPError) as cm: - call_api(url.format(token), dict( - action='accept', - )) - self.assertEqual(cm.exception.code, 204) + content, response = call_api(url.format(token), dict( + action='accept', + )) + self.assertEqual(response.status, 204) # Anne is a member. self.assertEqual( - self._mlist.get_members('anne@example.com').address, + self._mlist.members.get_member('anne@example.com').address, self._anne) # The request URL no longer exists. with self.assertRaises(HTTPError) as cm: @@ -177,19 +198,27 @@ class TestSubscriptionModeration(unittest.TestCase): )) 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://localhost:9001/3.0/lists/ant@example.com' + '/requests/bogus', + dict(action='accept')) + self.assertEqual(cm.exception.code, 404) + def test_discard(self): # POST to the request to discard it. - token, token_owner, member = self._registrar.register(self._anne) + with transaction(): + token, token_owner, member = self._registrar.register(self._anne) # Anne's subscription request got held. self.assertIsNone(member) url = 'http://localhost:9001/3.0/lists/ant@example.com/requests/{}' - with self.assertRaises(HTTPError) as cm: - call_api(url.format(token), dict( - action='discard', - )) - self.assertEqual(cm.exception.code, 204) + content, response = call_api(url.format(token), dict( + action='discard', + )) + self.assertEqual(response.status, 204) # Anne is not a member. - self.assertIsNone(self._mlist.get_members('anne@example.com')) + self.assertIsNone(self._mlist.members.get_member('anne@example.com')) # The request URL no longer exists. with self.assertRaises(HTTPError) as cm: call_api(url.format(token), dict( @@ -199,32 +228,30 @@ class TestSubscriptionModeration(unittest.TestCase): def test_defer(self): # Defer the decision for some other moderator. - token, token_owner, member = self._registrar.register(self._anne) + with transaction(): + token, token_owner, member = self._registrar.register(self._anne) # Anne's subscription request got held. self.assertIsNone(member) url = 'http://localhost:9001/3.0/lists/ant@example.com/requests/{}' - with self.assertRaises(HTTPError) as cm: - call_api(url.format(token), dict( - action='defer', - )) - self.assertEqual(cm.exception.code, 204) + content, response = call_api(url.format(token), dict( + action='defer', + )) + self.assertEqual(response.status, 204) # Anne is not a member. - self.assertIsNone(self._mlist.get_members('anne@example.com')) + self.assertIsNone(self._mlist.members.get_member('anne@example.com')) # The request URL still exists. - with self.assertRaises(HTTPError) as cm: - call_api(url.format(token), dict( - action='defer', - )) - self.assertEqual(cm.exception.code, 204) + content, response = call_api(url.format(token), dict( + action='defer', + )) + self.assertEqual(response.status, 204) # And now we can accept it. - with self.assertRaises(HTTPError) as cm: - call_api(url.format(token), dict( - action='accept', - )) - self.assertEqual(cm.exception.code, 204) + content, response = call_api(url.format(token), dict( + action='accept', + )) + self.assertEqual(response.status, 204) # Anne is a member. self.assertEqual( - self._mlist.get_members('anne@example.com').address, + self._mlist.members.get_member('anne@example.com').address, self._anne) # The request URL no longer exists. with self.assertRaises(HTTPError) as cm: @@ -233,23 +260,31 @@ class TestSubscriptionModeration(unittest.TestCase): )) 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://localhost:9001/3.0/lists/ant@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. - token, token_owner, member = self._registrar.register(self._anne) + with transaction(): + token, token_owner, member = self._registrar.register(self._anne) # Anne's subscription request got held. self.assertIsNone(member) # There are currently no messages in the virgin queue. items = get_queue_messages('virgin') self.assertEqual(len(items), 0) url = 'http://localhost:9001/3.0/lists/ant@example.com/requests/{}' - with self.assertRaises(HTTPError) as cm: - call_api(url.format(token), dict( - action='reject', - )) - self.assertEqual(cm.exception.code, 204) + content, response = call_api(url.format(token), dict( + action='reject', + )) + self.assertEqual(response.status, 204) # Anne is not a member. - self.assertIsNone(self._mlist.get_members('anne@example.com')) + self.assertIsNone(self._mlist.members.get_member('anne@example.com')) # The request URL no longer exists. with self.assertRaises(HTTPError) as cm: call_api(url.format(token), dict( @@ -260,3 +295,11 @@ class TestSubscriptionModeration(unittest.TestCase): items = get_queue_messages('virgin') self.assertEqual(len(items), 1) self.assertEqual(str(items[0].msg), '') + + def test_reject_bad_token(self): + # Try to accept a request with a bogus token. + with self.assertRaises(HTTPError) as cm: + call_api('http://localhost:9001/3.0/lists/ant@example.com' + '/requests/bogus', + dict(action='reject')) + self.assertEqual(cm.exception.code, 404) |
