summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/app/subscriptions.py5
-rw-r--r--src/mailman/app/tests/test_subscriptions.py5
-rw-r--r--src/mailman/rest/docs/post-moderation.rst1
-rw-r--r--src/mailman/rest/docs/sub-moderation.rst8
-rw-r--r--src/mailman/rest/sub_moderation.py89
-rw-r--r--src/mailman/rest/tests/test_moderation.py131
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)