Jimmy Bergman has proposed merging
lp:~jimmy-sigint/mailman/rest_api_ban_management into lp:mailman.
Requested reviews:
Mailman Coders (mailman-coders)
For more details, see:
https://code.launchpad.net/~jimmy-sigint/mailman/rest_api_ban_management/+merge/125648
This branch adds a REST API resource for managing per list bans, which is useful
in control panels targetting mailman 3.
See the src/mailman/rest/docs/membership.rst patch for usage.
--
https://code.launchpad.net/~jimmy-sigint/mailman/rest_api_ban_management/+merge/125648
Your team Mailman Coders is requested to review the proposed merge of
lp:~jimmy-sigint/mailman/rest_api_ban_management into lp:mailman.
=== modified file 'src/mailman/interfaces/bans.py'
--- src/mailman/interfaces/bans.py 2012-01-01 19:14:46 +0000
+++ src/mailman/interfaces/bans.py 2012-09-21 09:06:20 +0000
@@ -108,3 +108,13 @@
or not.
:rtype: bool
"""
+
+ def ban_list(mailing_list):
+ """Retrieves a list of bans for a specific mailing list.
+
+ :param mailing_list: The fqdn name of the mailing list to retrieve
+ the ban list for.
+ :type mailing_list: string
+ :return: A list of banned emails.
+ :rtype: list
+ """
=== modified file 'src/mailman/model/bans.py'
--- src/mailman/model/bans.py 2012-04-26 02:08:22 +0000
+++ src/mailman/model/bans.py 2012-09-21 09:06:20 +0000
@@ -109,3 +109,8 @@
re.match(ban.email, email, re.IGNORECASE) is not None):
return True
return False
+
+ @dbconnection
+ def ban_list(self, store, mailing_list):
+ """See `IBanManager`."""
+ return [ ban.email for ban in store.find(Ban, mailing_list=mailing_list) ]
=== modified file 'src/mailman/rest/docs/membership.rst'
--- src/mailman/rest/docs/membership.rst 2012-09-05 01:31:50 +0000
+++ src/mailman/rest/docs/membership.rst 2012-09-21 09:06:20 +0000
@@ -597,6 +597,51 @@
>>> set(member.mailing_list for member in elly.memberships.members)
set([])
+Handling the list of banned addresses
+=====================================
+
+To ban an address from subscribing you can POST to the /banlist child
+of any list using the REST API.
+::
+
+ # Ensure our previous reads don't keep the database lock.
+ >>> transaction.abort()
+ >>> dump_json('http://localhost:9001/3.0/lists/[email protected]'
+ ... '/banlist', { 'address': '[email protected]' })
+ content-length: 0
+ ...
+ status: 201
+
+This address is now banned, and you can get the list of banned addresses using
+the REST API:
+
+ # Ensure our previous reads don't keep the database lock.
+ >>> transaction.abort()
+ >>> dump_json('http://localhost:9001/3.0/lists/[email protected]/banlist')
+ entry 0:
+ banned_email: [email protected]
+ ...
+
+Or checking if a single address is banned:
+
+ >>> dump_json('http://localhost:9001/3.0/lists/[email protected]/banlist/[email protected]')
+ banned_email: [email protected]
+ http_etag: ...
+
+Unbanning addresses is also possible using the REST API.
+
+ >>> dump_json('http://localhost:9001/3.0/lists/[email protected]/banlist'
+ ... '/[email protected]', method='DELETE')
+ content-length: 0
+ ...
+ status: 200
+
+After unbanning it shouldn't be available in the ban list:
+
+ >>> dump_json('http://localhost:9001/3.0/lists/[email protected]/banlist/[email protected]')
+ Traceback (most recent call last):
+ ...
+ HTTPError: HTTP Error 404: 404 Not Found
Digest delivery
===============
=== modified file 'src/mailman/rest/lists.py'
--- src/mailman/rest/lists.py 2012-09-05 01:31:50 +0000
+++ src/mailman/rest/lists.py 2012-09-21 09:06:20 +0000
@@ -23,6 +23,8 @@
__all__ = [
'AList',
'AllLists',
+ 'BannedAddress',
+ 'BannedAddresses',
'ListConfiguration',
'ListsForDomain',
]
@@ -33,6 +35,7 @@
from zope.component import getUtility
from mailman.app.lifecycle import create_list, remove_list
+from mailman.interfaces.bans import IBanManager
from mailman.interfaces.domain import BadDomainSpecificationError
from mailman.interfaces.listmanager import (
IListManager, ListAlreadyExistsError)
@@ -82,6 +85,30 @@
@restish_matcher
+def banlist_matcher(request, segments):
+ """A matcher of all banned addresses for a mailing list.
+
+ e.g. /banlist
+ """
+ if len(segments) != 1 or segments[0] != 'banlist':
+ return None
+ else:
+ return (), {}, ()
+
+
+@restish_matcher
+def banned_matcher(request, segments):
+ """A matcher of a single banned addresses for a mailing list.
+
+ e.g. /banlist/[email protected]
+ """
+ if len(segments) != 2 or segments[0] != 'banlist':
+ return None
+ else:
+ return (), dict(address=segments[1]), ()
+
+
+@restish_matcher
def config_matcher(request, segments):
"""A matcher for a mailing list's configuration resource.
@@ -173,6 +200,86 @@
return http.not_found()
return HeldMessages(self._mlist)
+ @resource.child(banlist_matcher)
+ def banlist(self, request, segments, attribute=None):
+ """Return a list of banned addresses."""
+ return BannedAddresses(self._mlist)
+
+ @resource.child(banned_matcher)
+ def banned(self, request, segments, address):
+ """Return a list of banned addresses."""
+ return BannedAddress(self._mlist, address)
+
+
+
+class BannedAddress(resource.Resource, CollectionMixin):
+ """Class to represent a banned address."""
+
+ def __init__(self, mlist, address):
+ self._mlist = mlist
+ self._address = address
+ self.ban_manager = getUtility(IBanManager)
+
+ @resource.GET()
+ def container(self, request):
+ """/banlist/[email protected]"""
+ if self._mlist is None or self._address is None or not self.ban_manager.is_banned(self._address, self._mlist.fqdn_listname):
+ return http.not_found()
+ resource = dict(banned_email=self._address)
+ return http.ok([], etag(resource))
+
+ @resource.DELETE()
+ def remove(self, request):
+ """Remove an address from the ban list."""
+ if self._mlist is None or self._address is None:
+ return http.not_found()
+ else:
+ if not self.ban_manager.is_banned(self._address, self._mlist.fqdn_listname):
+ return http.bad_request([], b'Address is not in ban list')
+
+ self.ban_manager.unban(self._address, self._mlist.fqdn_listname)
+ return http.ok([], '')
+
+class BannedAddresses(resource.Resource, CollectionMixin):
+ """Class to represent the list of all banned addresses."""
+
+ def __init__(self, mlist):
+ self._mlist = mlist
+ self.ban_manager = getUtility(IBanManager)
+
+ def _resource_as_dict(self, item):
+ """See `CollectionMixin`."""
+ return dict(
+ banned_email=item,
+ )
+
+ def _get_collection(self, request):
+ """See `CollectionMixin`."""
+ return self.ban_manager.ban_list(self._mlist.fqdn_listname)
+
+ @resource.GET()
+ def container(self, request):
+ """/banlist"""
+ resource = self._make_collection(request)
+ return http.ok([], etag(resource))
+
+ @resource.POST()
+ def create(self, request):
+ """Ban some address from subscribing."""
+ try:
+ validator = Validator(address=unicode)
+ converted = validator(request)
+
+ address = converted['address']
+ if self.ban_manager.is_banned(address, self._mlist.fqdn_listname):
+ return http.bad_request([], b'Address is already in ban list')
+
+ self.ban_manager.ban(address, self._mlist.fqdn_listname)
+ except ValueError as error:
+ return http.bad_request([], str(error))
+ location = path_to('lists/{0}/banlist'.format(self._mlist.fqdn_listname))
+ return http.created(location, [], None)
+
class AllLists(_ListBase):
_______________________________________________
Mailman-coders mailing list
[email protected]
http://mail.python.org/mailman/listinfo/mailman-coders