diff options
Diffstat (limited to 'src/mailman/rest/webservice.py')
| -rw-r--r-- | src/mailman/rest/webservice.py | 179 |
1 files changed, 158 insertions, 21 deletions
diff --git a/src/mailman/rest/webservice.py b/src/mailman/rest/webservice.py index 40e4a5ef6..d7f7d231e 100644 --- a/src/mailman/rest/webservice.py +++ b/src/mailman/rest/webservice.py @@ -39,15 +39,19 @@ from wsgiref.simple_server import ( from zope.component import getUtility from zope.interface import implements +from mailman.app.membership import delete_member from mailman.config import config from mailman.core.system import system +from mailman.interfaces.address import InvalidEmailAddressError from mailman.interfaces.domain import ( BadDomainSpecificationError, IDomain, IDomainManager) -from mailman.interfaces.listmanager import ListAlreadyExistsError, IListManager +from mailman.interfaces.listmanager import ( + IListManager, ListAlreadyExistsError, NoSuchListError) from mailman.interfaces.mailinglist import IMailingList -from mailman.interfaces.member import IMember +from mailman.interfaces.member import ( + AlreadySubscribedError, IMember, MemberRole) from mailman.interfaces.membership import ISubscriptionService -from mailman.interfaces.rest import IResolvePathNames +from mailman.interfaces.rest import APIValueError, IResolvePathNames #from mailman.rest.publication import AdminWebServicePublication @@ -92,12 +96,18 @@ class TopLevel(resource.Resource): def lists(self, request, segments): if len(segments) == 0: return AllLists() - elif len(segments) == 1: - return AList(segments[0]), [] else: - return http.bad_request() + list_name = segments.pop(0) + return AList(list_name), segments + @resource.child() + def members(self, request, segments): + if len(segments) == 0: + return AllMembers() + return http.bad_request() + + class _DomainBase(resource.Resource): """Shared base class for domain representations.""" @@ -143,12 +153,9 @@ class AllDomains(_DomainBase): # introspection of the target method, or via descriptors. domain_manager = getUtility(IDomainManager) try: - # Hmmm... webob gives this to us as a string, but we need - # unicodes. For backward compatibility with lazr.restful style - # requests, ignore any ws.op parameter. + # webob gives this to us as a string, but we need unicodes. kws = dict((key, unicode(value)) - for key, value in request.POST.items() - if key != 'ws.op') + for key, value in request.POST.items()) domain = domain_manager.add(**kws) except BadDomainSpecificationError: return http.bad_request([], 'Domain exists') @@ -181,6 +188,7 @@ class AllDomains(_DomainBase): return http.ok([], json.dumps(response)) + class _ListBase(resource.Resource): """Shared base class for mailing list representations.""" @@ -200,19 +208,43 @@ class _ListBase(resource.Resource): return list_data +def member_matcher(request, segments): + """A matcher of member URLs inside mailing lists. + + e.g. /member/aperson@example.org + """ + if len(segments) != 2: + return None + try: + role = MemberRole[segments[0]] + except ValueError: + # Not a valid role. + return None + # No more segments. + return (), dict(role=role, address=segments[1]), () + +# XXX 2010-02-24 barry Seems like contrary to the documentation, matchers +# cannot be plain function, because matchers must have a .score attribute. +# OTOH, I think they support regexps, so that might be a better way to go. +member_matcher.score = () + + class AList(_ListBase): """A mailing list.""" - def __init__(self, mlist): - self._mlist = mlist + def __init__(self, list_name): + self._mlist = getUtility(IListManager).get(list_name) @resource.GET() def mailing_list(self, request): """Return a single mailing list end-point.""" - mlist = getUtility(IListManager).get(self._mlist) - if mlist is None: + if self._mlist is None: return http.not_found() - return http.ok([], json.dumps(self._format_list(mlist))) + return http.ok([], json.dumps(self._format_list(self._mlist))) + + @resource.child(member_matcher) + def member(self, request, segments, role, address): + return AMember(self._mlist, role, address) class AllLists(_ListBase): @@ -225,12 +257,9 @@ class AllLists(_ListBase): # introspection of the target method, or via descriptors. list_manager = getUtility(IListManager) try: - # Hmmm... webob gives this to us as a string, but we need - # unicodes. For backward compatibility with lazr.restful style - # requests, ignore any ws.op parameter. + # webob gives this to us as a string, but we need unicodes. kws = dict((key, unicode(value)) - for key, value in request.POST.items() - if key != 'ws.op') + for key, value in request.POST.items()) mlist = list_manager.new(**kws) except ListAlreadyExistsError: return http.bad_request([], b'Mailing list exists') @@ -266,6 +295,114 @@ class AllLists(_ListBase): return http.ok([], json.dumps(response)) + +class _MemberBase(resource.Resource): + """Shared base class for member representations.""" + + def _format_member(self, member): + """Format the data for a single member.""" + enum, dot, role = str(member.role).partition('.') + member_data = dict( + resource_type_link='http://localhost:8001/3.0/#member', + self_link='http://localhost:8001/3.0/lists/{0}/{1}/{2}'.format( + member.mailing_list, role, member.address.address), + ) + etag = hashlib.sha1(repr(member_data)).hexdigest() + member_data['http_etag'] = '"{0}"'.format(etag) + return member_data + + +class AMember(_MemberBase): + """A member.""" + + def __init__(self, mailing_list, role, address): + self._mlist = mailing_list + self._role = role + self._address = address + # XXX 2010-02-24 barry There should be a more direct way to get a + # member out of a mailing list. + if self._role is MemberRole.member: + roster = self._mlist.members + elif self._role is MemberRole.owner: + roster = self._mlist.owners + elif self._role is MemberRole.moderator: + roster = self._mlist.moderators + else: + raise AssertionError( + 'Undefined MemberRole: {0}'.format(self._role)) + self._member = roster.get_member(self._address) + + @resource.GET() + def member(self, request): + """Return a single member end-point.""" + return http.ok([], json.dumps(self._format_member(self._member))) + + @resource.DELETE() + def delete(self, request): + """Delete the member (i.e. unsubscribe).""" + # Leaving a list is a bit different than deleting a moderator or + # owner. Handle the former case first. For now too, we will not send + # an admin or user notification. + if self._role is MemberRole.member: + delete_member(self._mlist, self._address, False, False) + else: + self._member.unsubscribe() + return http.ok([], '') + + +class AllMembers(_MemberBase): + """The members.""" + + @resource.POST() + def create(self, request): + """Create a new member.""" + # XXX 2010-02-23 barry Sanity check the POST arguments by + # introspection of the target method, or via descriptors. + service = getUtility(ISubscriptionService) + try: + # webob gives this to us as a string, but we need unicodes. + kws = dict((key, unicode(value)) + for key, value in request.POST.items()) + member = service.join(**kws) + except AlreadySubscribedError: + return http.bad_request([], b'Member already subscribed') + except NoSuchListError: + return http.bad_request([], b'No such list') + except InvalidEmailAddressError: + return http.bad_request([], b'Invalid email address') + except APIValueError as error: + return http.bad_request([], str(error)) + # wsgiref wants headers to be bytes, not unicodes. + location = b'http://localhost:8001/3.0/lists/{0}/member/{1}'.format( + member.mailing_list, member.address.address) + # Include no extra headers or body. + return http.created(location, [], None) + + @resource.GET() + def container(self, request): + """Return the /members end-point.""" + members = list(getUtility(ISubscriptionService)) + if len(members) == 0: + return http.ok( + [], json.dumps(dict(resource_type_link= + 'http://localhost:8001/3.0/#members', + start=None, + total_size=0))) + entries = [] + response = dict( + resource_type_link='http://localhost:8001/3.0/#members', + start=0, + total_size=len(members), + entries=entries, + ) + for member in members: + member_data = self._format_member(member) + entries.append(member_data) + return http.ok([], json.dumps(response)) + + + + ## class AdminWebServiceRootResource(RootResource): ## """The lazr.restful non-versioned root resource.""" |
