Skip to content

Commit

Permalink
Allow XML-RPC view methods to take parameters.
Browse files Browse the repository at this point in the history
When the XML-RPC view setup machinery was changed recently, views taking parameters
were not tested.  The problem was that methods were security protected using
an instance of ProtectedMethod which implemented ILocation (thus could inherit
security context) and provides a __call__(*args) and passes *args onto the
actual view method.  THis works fine when no parameters are issues via xml-rpc,
but when they are, zope.publisher.publish.mapply chokes.  For some reason, it
cannot figure out the *args thing.

This fix gets rid of MethodFactory and ProtectedMethod.  Instead, a new view
class with a security checker and a __call__ that is a copy of the method that
is to be used is created.  This process is very similar to what happens with
browser pages.

README.txt was updated with a functional test for this.
  • Loading branch information
philikon committed Aug 29, 2004
1 parent b6d4c85 commit 2ffbcb6
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 39 deletions.
66 changes: 66 additions & 0 deletions xmlrpc/README.txt
Expand Up @@ -245,6 +245,72 @@ as before, we will get an error if we don't supply credentials:
</methodResponse>
<BLANKLINE>

Parameters
----------

Of course, XML-RPC views can take parameters, too:

>>> class ParameterDemo:
... def __init__(self, context, request):
... self.context = context
... self.request = request
...
... def add(self, first, second):
... return first + second

Now we'll register it as a view:

>>> from zope.configuration import xmlconfig
>>> ignored = xmlconfig.string("""
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
... >
... <!-- We only need to do this include in this example,
... Normally the include has already been done for us. -->
... <include package="zope.app.publisher.xmlrpc" file="meta.zcml" />
...
... <xmlrpc:view
... for="zope.app.folder.folder.IFolder"
... methods="add"
... class="zope.app.publisher.xmlrpc.README.ParameterDemo"
... permission="zope.ManageContent"
... />
... </configure>
... """)

Then we can issue a remote procedure call with a parameter and get
back, surprise!, the sum:

>>> print http(r"""
... POST / HTTP/1.0
... Authorization: Basic bWdyOm1ncnB3
... Content-Length: 159
... Content-Type: text/xml
...
... <?xml version='1.0'?>
... <methodCall>
... <methodName>add</methodName>
... <params>
... <param><int>20</int></param>
... <param><int>22</int></param>
... </params>
... </methodCall>
... """, handle_errors=False)
HTTP/1.0 200 Ok
Content-Length: 122
Content-Type: text/xml;charset=utf-8
<BLANKLINE>
<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><int>42</int></value>
</param>
</params>
</methodResponse>
<BLANKLINE>

Faults
------

Expand Down
52 changes: 13 additions & 39 deletions xmlrpc/metaconfigure.py
Expand Up @@ -15,17 +15,15 @@
$Id$
"""

import zope.interface
from zope.security.checker import CheckerPublic, Checker
from zope.component.servicenames import Presentation
from zope.configuration.exceptions import ConfigurationError
from zope.app.component.metaconfigure import handler
import zope.interface

from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
from zope.security.checker import CheckerPublic, Checker
from zope.app.component.interface import provideInterface

import zope.app.location
from zope.app.location import Location
from zope.app.component.interface import provideInterface
from zope.app.component.metaconfigure import handler

def view(_context, for_=None, interface=None, methods=None,
class_=None, permission=None, name=None):
Expand Down Expand Up @@ -77,14 +75,19 @@ def proxyView(context, request, class_=class_, checker=checker):
checker = Checker({'__call__': permission})
else:
checker = None

for name in require:
# create a new callable class with a security checker; mix
# in zope.app.location.Location so that the view inherits
# a security context
cdict = {'__Security_checker__': checker,
'__call__': getattr(class_, name)}
new_class = type(class_.__name__, (class_, Location), cdict)
_context.action(
discriminator = ('view', for_, name, IXMLRPCRequest),
callable = handler,
args = (Presentation, 'provideAdapter', IXMLRPCRequest,
MethodFactory(class_, name, checker),
name, (for_, )) )
new_class, name, (for_, )) )

# Register the used interfaces with the interface service
if for_ is not None:
Expand All @@ -93,32 +96,3 @@ def proxyView(context, request, class_=class_, checker=checker):
callable = provideInterface,
args = ('', for_)
)



class MethodFactory:

def __init__(self, cls, name, checker):
self.cls, self.name, self.checker = cls, name, checker

def __call__(self, context, request):
ob = self.cls(context, request)
ob = getattr(ob, self.name)
if self.checker is not None:
ob = ProtectedMethod(ob, self.checker)
return ob


class ProtectedMethod:

zope.interface.implements(zope.app.location.ILocation)

__parent__ = __name__ = None

def __init__(self, ob, checker):
self.ob = ob
self.__Security_checker__ = checker

def __call__(self, *args):
return self.ob(*args)

0 comments on commit 2ffbcb6

Please sign in to comment.