diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/interfaces/rest.py | 6 | ||||
| -rw-r--r-- | src/mailman/rest/configuration.py | 9 | ||||
| -rw-r--r-- | src/mailman/rest/configure.zcml | 12 | ||||
| -rw-r--r-- | src/mailman/rest/publication.py | 67 | ||||
| -rw-r--r-- | src/mailman/rest/root.py | 4 | ||||
| -rw-r--r-- | src/mailman/rest/traverse.py | 1 | ||||
| -rw-r--r-- | src/mailman/rest/urls.py | 53 | ||||
| -rw-r--r-- | src/mailman/rest/webservice.py | 88 |
8 files changed, 179 insertions, 61 deletions
diff --git a/src/mailman/interfaces/rest.py b/src/mailman/interfaces/rest.py index 697c81da4..83a458c1d 100644 --- a/src/mailman/interfaces/rest.py +++ b/src/mailman/interfaces/rest.py @@ -21,7 +21,7 @@ from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ - 'IHasGet', + 'IResolvePathNames', ] @@ -29,8 +29,8 @@ from zope.interface import Interface -class IHasGet(Interface): - """A marker interface objects that implement traversal with get().""" +class IResolvePathNames(Interface): + """A marker interface objects that implement simple traversal.""" def get(name): """Traverse to a contained object.""" diff --git a/src/mailman/rest/configuration.py b/src/mailman/rest/configuration.py index 9716f8503..0b142ca55 100644 --- a/src/mailman/rest/configuration.py +++ b/src/mailman/rest/configuration.py @@ -26,11 +26,12 @@ __all__ = [ from lazr.restful.interfaces import IWebServiceConfiguration +from zope.interface import implements +from mailman import version from mailman.config import config from mailman.rest.publication import AdminWebServicePublication from mailman.rest.root import AdminWebServiceRootResource -from mailman.version import VERSION @@ -43,7 +44,7 @@ class AdminWebServiceConfiguration: def view_permission(self): return config.webservice.view_permission - path_override = 'admin' + path_override = None @property def use_https(self): @@ -51,8 +52,8 @@ class AdminWebServiceConfiguration: return config.webservice.use_https # This should match the major.minor Mailman version. - service_version_uri_prefix = '3.0' - code_revision = VERSION + service_version_uri_prefix = '{0.MAJOR_REV}.{0.MINOR_REV}'.format(version) + code_revision = version.VERSION @property def show_tracebacks(self): diff --git a/src/mailman/rest/configure.zcml b/src/mailman/rest/configure.zcml index 24712b340..edd444d0d 100644 --- a/src/mailman/rest/configure.zcml +++ b/src/mailman/rest/configure.zcml @@ -6,16 +6,20 @@ <include package="zope.component" file="meta.zcml"/> <include package="zope.security" file="meta.zcml"/> <include package="lazr.restful" file="meta.zcml"/> + <include package="lazr.restful" file="configure.zcml"/> - <webservice:register module="mailman.interfaces.rest" /> + <webservice:register module="mailman.interfaces.system" /> - <adapter factory="mailman.rest.traverse.Traverse" /> - - <adapter factory="mailman.rest.root.AdminWebServiceRootAbsoluteURL" /> + <adapter factory="mailman.rest.urls.AbsoluteURLMapper" /> <adapter factory="mailman.rest.root.AdminWebServiceRootAbsoluteURL" name="absolute_url" /> + <utility + factory="mailman.rest.configuration.AdminWebServiceConfiguration" + provides="lazr.restful.interfaces.IWebServiceConfiguration"> + </utility> + </configure> diff --git a/src/mailman/rest/publication.py b/src/mailman/rest/publication.py index b5fac2318..bc57bdd58 100644 --- a/src/mailman/rest/publication.py +++ b/src/mailman/rest/publication.py @@ -28,80 +28,55 @@ __all__ = [ import traceback from lazr.restful.publisher import WebServicePublicationMixin -from zope.component import getUtility, queryMultiAdapter +from zope.component import queryMultiAdapter from zope.interface import implements -from zope.publisher.interfaces import IPublication, IPublishTraverse, NotFound +from zope.publisher.interfaces import IPublication, NotFound from zope.publisher.publish import mapply -from zope.security.checker import ProxyFactory from zope.security.management import endInteraction, newInteraction +from mailman.interfaces.rest import IResolvePathNames + class Publication: - """Very simple implementation of `IPublication`. - - The object pass to the constructor is returned by getApplication(). - """ + """Very simple implementation of `IPublication`.""" implements(IPublication) def __init__(self, application): - """Create the test publication. - - The object at which traversal should start is passed as parameter. - """ self.application = application def beforeTraversal(self, request): - """Sets the request as the current interaction. - - (It also ends any previous interaction, that's convenient when - tests don't go through the whole request.) - """ - endInteraction() - newInteraction(request) + """See `IPublication`.""" + pass def getApplication(self, request): - """Returns the application passed to the constructor.""" + """See `IPublication`.""" return self.application def callTraversalHooks(self, request, ob): - """Does nothing.""" + """See `IPublication`.""" + pass def traverseName(self, request, ob, name): - """Traverse by looking at an `IPublishTraverse` adapter. - - The object is security wrapped. - """ - # XXX flacoste 2009/03/06 bug=338831. This is copied from - # zope.app.publication.publicationtraverse.PublicationTraverse. - # This should really live in zope.publisher, we are copying because - # we don't want to depend on zope.app stuff. - # Namespace support was dropped. - if name == '.': - return ob - - if IPublishTraverse.providedBy(ob): - ob2 = ob.publishTraverse(request, name) - else: - # self is marker. - adapter = queryMultiAdapter( - (ob, request), IPublishTraverse, default=self) - if adapter is not self: - ob2 = adapter.publishTraverse(request, name) - else: - raise NotFound(ob, name, request) - - return ProxyFactory(ob2) + """See `IPublication`.""" + missing = object() + resolver = IResolvePathNames(ob, missing) + if resolver is missing: + raise NotFound(ob, name, request) + return ob.get(name) def afterTraversal(self, request, ob): - """Does nothing.""" + pass def callObject(self, request, ob): """Call the object, returning the result.""" + # XXX Bad hack. + from zope.security.proxy import removeSecurityProxy + ob = removeSecurityProxy(ob) return mapply(ob, request.getPositionalArguments(), request) def afterCall(self, request, ob): - """Does nothing.""" + pass def handleException(self, object, request, exc_info, retry_allowed=1): """Prints the exception.""" diff --git a/src/mailman/rest/root.py b/src/mailman/rest/root.py index a38371770..620739287 100644 --- a/src/mailman/rest/root.py +++ b/src/mailman/rest/root.py @@ -27,6 +27,7 @@ __all__ = [ from lazr.restful import ServiceRootResource +from lazr.restful.interfaces import ICollection from zope.component import adapts from zope.interface import implements from zope.publisher.interfaces.browser import IDefaultBrowserLayer @@ -34,15 +35,12 @@ from zope.traversing.browser.interfaces import IAbsoluteURL from mailman.config import config from mailman.core.system import system -from mailman.interfaces.rest import IHasGet class AdminWebServiceRootResource(ServiceRootResource): """The root of the Mailman RESTful admin web service.""" - implements(IHasGet) - def get(self, name): """See `IHasGet`.""" top_level = { diff --git a/src/mailman/rest/traverse.py b/src/mailman/rest/traverse.py index 34d747c32..2aef47709 100644 --- a/src/mailman/rest/traverse.py +++ b/src/mailman/rest/traverse.py @@ -40,7 +40,6 @@ class Traverse: """An implementation of `IPublishTraverse` that uses the get() method.""" implements(IPublishTraverse) - adapts(IHasGet, IDefaultBrowserLayer) def __init__(self, context, request): self.context = context diff --git a/src/mailman/rest/urls.py b/src/mailman/rest/urls.py new file mode 100644 index 000000000..0cd51e5f6 --- /dev/null +++ b/src/mailman/rest/urls.py @@ -0,0 +1,53 @@ +# Copyright (C) 2009 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/>. + +"""Module stuff.""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'AbsoluteURLMapper', + ] + + +from zope.component import adapts +from zope.interface import implements, Interface +from zope.traversing.browser.interfaces import IAbsoluteURL + + + +class AbsoluteURLMapper: + """Generic absolute url mapper.""" + + implements(IAbsoluteURL) + adapts(Interface, IAbsoluteURL) + + def __init__(self, context, request): + """Initialize with respect to a context and request.""" + # Avoid circular imports. + from mailman.rest.configuration import AdminWebServiceConfiguration + self.webservice_config = AdminWebServiceConfiguration() + self.version = webservice_config.service_version_uri_prefix + self.schema = ('https' if self.webservice_config.use_https else 'http') + self.hostname = config.webservice.hostname + + def __str__(self): + """Return the semi-hard-coded URL to the service root.""" + return '{0.schema}://{0.hostname}/{0.version}'.format(self) + + __call__ = __str__ diff --git a/src/mailman/rest/webservice.py b/src/mailman/rest/webservice.py new file mode 100644 index 000000000..8203b0439 --- /dev/null +++ b/src/mailman/rest/webservice.py @@ -0,0 +1,88 @@ +# Copyright (C) 2009 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/>. + +"""Module stuff.""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'AdminWebServiceApplication', + 'AdminWebServiceRequest', + 'start', + ] + + +from wsgiref.simple_server import make_server + +from lazr.restful.publisher import WebServiceRequestTraversal +from pkg_resources import resource_string +from zope.configuration import xmlconfig +from zope.interface import implements +from zope.publisher.browser import BrowserRequest +from zope.publisher.publish import publish + +from mailman.core.system import system +from mailman.interfaces.rest import IResolvePathNames +from mailman.rest.publication import AdminWebServicePublication + + + +class AdminWebServiceRequest(WebServiceRequestTraversal, BrowserRequest): + """A request for the admin REST interface.""" + + +class AdminWebServiceApplication: + """A WSGI application for the admin REST interface.""" + + implements(IResolvePathNames) + + def __init__(self, environ, start_response): + # Create the request based on the HTTP method used. + method = environ.get('REQUEST_METHOD', 'GET').upper() + request = AdminWebServiceRequest(environ['wsgi.input'], environ) + request.setPublication(AdminWebServicePublication(self)) + # Support post-mortem debugging. + handle_errors = environ.get('wsgi.handleErrors', True) + # The request returned by the publisher may in fact be different than + # the one passed in. + request = publish(request, handle_errors=handle_errors) + # Start the WSGI server response. + response = request.response + start_response(response.getStatusString(), response.getHeaders()) + # Return the result body iterable. + return response.consumeBodyIter() + + def get(self, name): + """Maps root names to resources.""" + top_level = dict( + sys=system, + ) + return top_level.get(name) + + + +def start(): + """Start the WSGI admin REST service.""" + zcml = resource_string('mailman.rest', 'configure.zcml') + xmlconfig.string(zcml) + server = make_server('', 8001, AdminWebServiceApplication) + return server + + +if __name__ == '__main__': + start().serve_forever() |
