diff options
Diffstat (limited to 'src/mailman/rest')
| -rw-r--r-- | src/mailman/rest/bans.py | 109 | ||||
| -rw-r--r-- | src/mailman/rest/docs/membership.rst | 81 | ||||
| -rw-r--r-- | src/mailman/rest/lists.py | 8 | ||||
| -rw-r--r-- | src/mailman/rest/root.py | 12 |
4 files changed, 210 insertions, 0 deletions
diff --git a/src/mailman/rest/bans.py b/src/mailman/rest/bans.py new file mode 100644 index 000000000..bf331f46e --- /dev/null +++ b/src/mailman/rest/bans.py @@ -0,0 +1,109 @@ +# Copyright (C) 2015 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""REST for banned emails.""" + +__all__ = [ + 'BannedEmail', + 'BannedEmails', + ] + + +from mailman.interfaces.bans import IBanManager +from mailman.rest.helpers import ( + CollectionMixin, bad_request, child, created, etag, no_content, not_found, + okay) +from mailman.rest.validator import Validator + + + +class BannedEmail: + """A banned email.""" + + def __init__(self, mailing_list, email): + self._mlist = mailing_list + self.ban_manager = IBanManager(self._mlist) + self._email = email + + def on_get(self, request, response): + """Get a banned email.""" + if self._email is None: + bad_request(response, 'Invalid email') + elif not self.ban_manager.is_banned(self._email): + not_found( + response, 'Email {} is not banned'.format(self._email)) + else: + resource = dict(email=self._email) + okay(response, etag(resource)) + + def on_delete(self, request, response): + """Remove an email from the ban list.""" + if self._email is None: + bad_request(response, 'Invalid email') + elif not self.ban_manager.is_banned(self._email): + bad_request( + response, 'Email {} is not banned'.format(self._email)) + else: + self.ban_manager.unban(self._email) + no_content(response) + + +class BannedEmails(CollectionMixin): + """The list of all banned emails.""" + + def __init__(self, mailing_list): + self._mlist = mailing_list + self.ban_manager = IBanManager(self._mlist) + + def _resource_as_dict(self, ban): + """See `CollectionMixin`.""" + return dict( + email=ban.email, + list_id=ban.list_id, + ) + + def _get_collection(self, request): + """See `CollectionMixin`.""" + return list(self.ban_manager) + + def on_get(self, request, response): + """/bans""" + resource = self._make_collection(request) + okay(response, etag(resource)) + + def on_post(self, request, response): + """Ban some email from subscribing.""" + validator = Validator(email=str) + try: + email = validator(request)['email'] + except ValueError as error: + bad_request(response, str(error)) + return + if self.ban_manager.is_banned(email): + bad_request(response, b'Address is already banned') + else: + self.ban_manager.ban(email) + if self._mlist is None: + base_location = '' + else: + base_location = 'lists/{}/'.format(self._mlist.list_id) + location = self.path_to('{}bans/{}'.format(base_location, email)) + created(response, location) + + @child(r'^(?P<email>[^/]+)') + def email(self, request, segments, **kw): + return BannedEmail(self._mlist, kw['email']) diff --git a/src/mailman/rest/docs/membership.rst b/src/mailman/rest/docs/membership.rst index 0b00a8808..8470eb3c6 100644 --- a/src/mailman/rest/docs/membership.rst +++ b/src/mailman/rest/docs/membership.rst @@ -965,3 +965,84 @@ The moderation action for a member can be changed by PATCH'ing the ... moderation_action: hold ... + + +Handling the list of banned addresses +===================================== + +To ban an address from subscribing you can POST to the /bans child +of any list using the REST API. +:: + + >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com/bans', + ... {'email': 'banned@example.com'}) + content-length: 0 + ... + location: http://localhost:9001/3.0/lists/ant.example.com/bans/banned@example.com + ... + status: 201 + +This address is now banned, and you can get the list of banned addresses by +issuing a GET request on the /bans child:: + + >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com/bans') + entry 0: + email: banned@example.com + ... + +Or checking if a single address is banned: + + >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com/bans/banned@example.com') + email: banned@example.com + http_etag: ... + >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com/bans/someone-else@example.com') + Traceback (most recent call last): + ... + urllib.error.HTTPError: HTTP Error 404: ... + +Unbanning addresses is also possible by issuing a DELETE request:: + + >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com/bans/banned@example.com', + ... method='DELETE') + content-length: 0 + ... + status: 204 + +After unbanning, the address is not shown in the ban list anymore:: + + >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com/bans/banned@example.com') + Traceback (most recent call last): + ... + urllib.error.HTTPError: HTTP Error 404: ... + >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com/bans') + http_etag: "..." + start: 0 + total_size: 0 + +To ban an address from subscribing to every list, you can use the global /bans endpoint:: + + >>> dump_json('http://localhost:9001/3.0/bans', + ... {'email': 'banned@example.com'}) + content-length: 0 + ... + status: 201 + >>> dump_json('http://localhost:9001/3.0/bans') + entry 0: + email: banned@example.com + ... + >>> dump_json('http://localhost:9001/3.0/bans/banned@example.com') + email: banned@example.com + http_etag: ... + >>> dump_json('http://localhost:9001/3.0/bans/banned@example.com', + ... method='DELETE') + content-length: 0 + ... + status: 204 + >>> dump_json('http://localhost:9001/3.0/bans/banned@example.com') + Traceback (most recent call last): + ... + urllib.error.HTTPError: HTTP Error 404: ... + >>> dump_json('http://localhost:9001/3.0/bans') + http_etag: "..." + start: 0 + total_size: 0 diff --git a/src/mailman/rest/lists.py b/src/mailman/rest/lists.py index 3e0c0bbca..42814857b 100644 --- a/src/mailman/rest/lists.py +++ b/src/mailman/rest/lists.py @@ -39,6 +39,7 @@ from mailman.interfaces.mailinglist import IListArchiverSet from mailman.interfaces.member import MemberRole from mailman.interfaces.styles import IStyleManager from mailman.interfaces.subscriptions import ISubscriptionService +from mailman.rest.bans import BannedEmails from mailman.rest.listconf import ListConfiguration from mailman.rest.helpers import ( CollectionMixin, GetterSetter, NotFound, accepted, bad_request, child, @@ -198,6 +199,13 @@ class AList(_ListBase): return NotFound(), [] return ListDigest(self._mlist) + @child() + def bans(self, request, segments): + """Return a collection of mailing list's banned addresses.""" + if self._mlist is None: + return NotFound(), [] + return BannedEmails(self._mlist) + class AllLists(_ListBase): diff --git a/src/mailman/rest/root.py b/src/mailman/rest/root.py index ca6e031e8..027ed3777 100644 --- a/src/mailman/rest/root.py +++ b/src/mailman/rest/root.py @@ -32,6 +32,7 @@ from mailman.core.system import system from mailman.interfaces.listmanager import IListManager from mailman.model.uid import UID from mailman.rest.addresses import AllAddresses, AnAddress +from mailman.rest.bans import BannedEmail, BannedEmails from mailman.rest.domains import ADomain, AllDomains from mailman.rest.helpers import ( BadRequest, NotFound, child, etag, no_content, not_found, okay) @@ -290,6 +291,17 @@ class TopLevel: return BadRequest(), [] @child() + def bans(self, request, segments): + """/<api>/bans + /<api>/bans/<email> + """ + if len(segments) == 0: + return BannedEmails(None) + else: + email = segments.pop(0) + return BannedEmail(None, email), segments + + @child() def reserved(self, request, segments): """/<api>/reserved/[...]""" return Reserved(segments), [] |
