summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAurélien Bompard2016-01-11 16:03:27 +0100
committerBarry Warsaw2016-01-13 14:20:16 -0500
commitad53d7612898b6ee12cd3daac449bed3a538dba4 (patch)
treeea1bdf20051ea4d82930fadd4d6ad40026b29aa1
parent187dad97bf278b0ca9d080774072e8fb235154cc (diff)
downloadmailman-ad53d7612898b6ee12cd3daac449bed3a538dba4.tar.gz
mailman-ad53d7612898b6ee12cd3daac449bed3a538dba4.tar.zst
mailman-ad53d7612898b6ee12cd3daac449bed3a538dba4.zip
-rw-r--r--src/mailman/interfaces/bans.py7
-rw-r--r--src/mailman/model/bans.py5
-rw-r--r--src/mailman/rest/bans.py109
-rw-r--r--src/mailman/rest/docs/membership.rst81
-rw-r--r--src/mailman/rest/lists.py8
-rw-r--r--src/mailman/rest/root.py12
6 files changed, 222 insertions, 0 deletions
diff --git a/src/mailman/interfaces/bans.py b/src/mailman/interfaces/bans.py
index b187743ab..3682be5f0 100644
--- a/src/mailman/interfaces/bans.py
+++ b/src/mailman/interfaces/bans.py
@@ -97,3 +97,10 @@ class IBanManager(Interface):
or not.
:rtype: bool
"""
+
+ def __iter__():
+ """Iterate over all banned addresses.
+
+ :return: The list of all banned addresses.
+ :rtype: list of `str`
+ """
diff --git a/src/mailman/model/bans.py b/src/mailman/model/bans.py
index a1ed24b76..0a3259167 100644
--- a/src/mailman/model/bans.py
+++ b/src/mailman/model/bans.py
@@ -111,3 +111,8 @@ class BanManager:
re.match(ban.email, email, re.IGNORECASE) is not None):
return True
return False
+
+ @dbconnection
+ def __iter__(self, store):
+ """See `IBanManager`."""
+ yield from store.query(Ban).filter_by(list_id=self._list_id)
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), []