# Copyright (C) 2012-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 . """REST API for Message moderation.""" __all__ = [ 'HeldMessage', 'HeldMessages', 'MembershipChangeRequest', 'SubscriptionRequests', ] from mailman.app.moderator import ( handle_message, handle_subscription, handle_unsubscription) from mailman.interfaces.action import Action from mailman.interfaces.messages import IMessageStore from mailman.interfaces.requests import IListRequests, RequestType from mailman.rest.helpers import ( CollectionMixin, bad_request, child, etag, no_content, not_found, okay) from mailman.rest.validator import Validator, enum_validator from zope.component import getUtility HELD_MESSAGE_REQUESTS = (RequestType.held_message,) MEMBERSHIP_CHANGE_REQUESTS = (RequestType.subscription, RequestType.unsubscription) class _ModerationBase: """Common base class.""" def _make_resource(self, request_id, expected_request_types): requests = IListRequests(self._mlist) results = requests.get_request(request_id) if results is None: return None key, data = results resource = dict(key=key, request_id=request_id) # Flatten the IRequest payload into the JSON representation. resource.update(data) # Check for a matching request type, and insert the type name into the # resource. request_type = RequestType[resource.pop('_request_type')] if request_type not in expected_request_types: return None resource['type'] = request_type.name # This key isn't what you think it is. Usually, it's the Pendable # record's row id, which isn't helpful at all. If it's not there, # that's fine too. resource.pop('id', None) return resource class _HeldMessageBase(_ModerationBase): """Held messages are a little different.""" def _make_resource(self, request_id): resource = super(_HeldMessageBase, self)._make_resource( request_id, HELD_MESSAGE_REQUESTS) if resource is None: return None # Grab the message and insert its text representation into the # resource. XXX See LP: #967954 key = resource.pop('key') msg = getUtility(IMessageStore).get_message_by_id(key) resource['msg'] = msg.as_string() # Some of the _mod_* keys we want to rename and place into the JSON # resource. Others we can drop. Since we're mutating the dictionary, # we need to make a copy of the keys. When you port this to Python 3, # you'll need to list()-ify the .keys() dictionary view. for key in list(resource): if key in ('_mod_subject', '_mod_hold_date', '_mod_reason', '_mod_sender', '_mod_message_id'): resource[key[5:]] = resource.pop(key) elif key.startswith('_mod_'): del resource[key] # Also, held message resources will always be this type, so ignore # this key value. del resource['type'] return resource class HeldMessage(_HeldMessageBase): """Resource for moderating a held message.""" def __init__(self, mlist, request_id): self._mlist = mlist self._request_id = request_id def on_get(self, request, response): try: request_id = int(self._request_id) except ValueError: bad_request(response) return resource = self._make_resource(request_id) if resource is None: not_found(response) else: okay(response, etag(resource)) def on_post(self, request, response): try: validator = Validator(action=enum_validator(Action)) arguments = validator(request) 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, RequestType.held_message) if results is None: not_found(response) else: handle_message(self._mlist, request_id, **arguments) no_content(response) class HeldMessages(_HeldMessageBase, CollectionMixin): """Resource for messages held for moderation.""" def __init__(self, mlist): self._mlist = mlist self._requests = None def _resource_as_dict(self, request): """See `CollectionMixin`.""" return self._make_resource(request.id) def _get_collection(self, request): requests = IListRequests(self._mlist) self._requests = requests return list(requests.of_type(RequestType.held_message)) def on_get(self, request, response): """/lists/listname/held""" resource = self._make_collection(request) okay(response, etag(resource)) @child(r'^(?P[^/]+)') def message(self, request, segments, **kw): return HeldMessage(self._mlist, kw['id']) class MembershipChangeRequest(_ModerationBase): """Resource for moderating a membership change.""" def __init__(self, mlist, request_id): self._mlist = mlist self._request_id = request_id def on_get(self, request, response): 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)) def on_post(self, request, response): try: validator = Validator(action=enum_validator(Action)) arguments = validator(request) 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) class SubscriptionRequests(_ModerationBase, CollectionMixin): """Resource for membership change requests.""" def __init__(self, mlist): self._mlist = mlist self._requests = None def _resource_as_dict(self, request): """See `CollectionMixin`.""" resource = self._make_resource(request.id, MEMBERSHIP_CHANGE_REQUESTS) # Remove unnecessary keys. del resource['key'] return resource def _get_collection(self, request): requests = IListRequests(self._mlist) self._requests = requests items = [] for request_type in MEMBERSHIP_CHANGE_REQUESTS: for request in requests.of_type(request_type): items.append(request) return items def on_get(self, request, response): """/lists/listname/requests""" resource = self._make_collection(request) okay(response, etag(resource)) @child(r'^(?P[^/]+)') def subscription(self, request, segments, **kw): return MembershipChangeRequest(self._mlist, kw['id'])