[Grok-dev] Re: HTTP PUT and HTTP DELETE security support
Martijn Faassen
faassen at startifact.com
Thu May 17 14:57:56 EDT 2007
Hey,
Christian Theune wrote:
> I'll have a look at the Zope 3 code in the train that I'm about to jump
> on.
Great! I've done a little bit of digging myself, today, so we should
compare notes.
I've been hacking in grok.publication. First I had to go and add the
following:
class GrokHTTPPublication(ZopePublicationSansProxy, HTTPPublication):
def getDefaultTraversal(self, request, ob):
obj, path = super(GrokHTTPPublication, self).getDefaultTraversal(
request, ob)
return removeSecurityProxy(obj), path
class GrokHTTPFactory(HTTPFactory):
def __call__(self):
request, publication = super(GrokHTTPFactory, self).__call__()
return request, GrokHTTPPublication
and register it like this:
<publisher
name="HTTP"
factory=".publication.GrokHTTPFactory"
methods="*"
mimetypes="*"
priority="1"
/>
The priority 1 makes it kick in before the default one in Zope.
Next, I ran into a snag with this code:
class ZopePublicationSansProxy(object):
...
def callObject(self, request, ob):
checker = selectChecker(ob)
if checker is not None:
checker.check(ob, '__call__')
return super(ZopePublicationSansProxy,
self).callObject(request, ob)
This code makes the assumption that a method '__call__' is always the
thing to check. This is true in case of the view, but that's not right
in case of PUT. Looks like I'll have to introduce my own version of
callObject for GrokHTTPPublication that doesn't make this assumption. I
hacked around this for now by disabling the whole checker thing altogether.
BrowserPublication handles GET and POST, where this assumption makes
sense. In case of PUT and DELETE, it'll try to call a method called PUT
or DELETE on a view registered for IHTTPRequest.
The Zope 3 code does the following, in zope.app.publication.http:
class HTTPPublication(BaseHTTPPublication):
"""HTTP-specific publication"""
def callObject(self, request, ob):
# Exception handling, dont try to call request.method
orig = ob
if not IHTTPException.providedBy(ob):
ob = zapi.queryMultiAdapter((ob, request), name=request.method)
ob = getattr(ob, request.method, None)
if ob is None:
raise MethodNotAllowed(orig, request)
return mapply(ob, request.getPositionalArguments(), request)
Note that the request object passed in is a IHTTPRequest, not a
IBrowserRequest. This means that registering a view for PUT doesn't
work. Instead you need a multi adapter:
class PUT(grok.MultiAdapter):
grok.adapts(IMyContent, IHTTPRequest)
grok.name('PUT')
grok.implements(Interface)
def __init__(self, obj, request):
pass
def PUT(self):
...
The grok.implements is necessary as MultiAdapters don't allow
registration otherwise. For HTTP-level views this makes little sense.
Unfortunately the name of the view *and* the method name needs to be
called PUT, so that's Repeating Yourself.
To support PUT on existing objects, we could define a new kind of PutView:
class MyPUT(grok.PutView):
grok.context(IMyContent)
def update(self, ...):
pass
def render(self):
pass
One snag is that the name of the put view doesn't haven't any meaning,
unlike the normal grok.View. PUT happens directly onto an object, not
onto a view. The rest makes sense though: a PUT can get parameters just
like a GET or a POST, which explains why you might want update(), and
can return results as well which explains render(). So perhapss we call
it grok.Put:
class MyPut(grok.Put):
pass
Actually it might not make as much sense as we want. The normal
grok.View does processing on the request arguments or POST data and
parses them into request.form. For PUT you'd typically want to get the
raw response body. We could have that be automatically be available on
the object (self.context, self.request, self.data), or alternatively we
can pass it as a data argument to 'update'.
class Put(grok.Put):
grok.context(IMyContent)
def update(self, data):
pass
def render(self):
pass
grok.Delete should be very similar.
The Ruby on Rails talk discusses higher level systems to make an
application get standard REST behavior. The pattern tends to be (also
gleaned from the Atom Publishing Protocol that I was pointed to):
GET on a container: get list of items in container (in XML, JSON, etc)
GET on container item: get container item data
POST on a container: create new object
PUT on container item: overwrite container item data
DELETE on container item: delete container item
Once we implement something like what I sketched above, we have most of
the ingredients to support this quite nicely in Grok, except for POST to
a container. I can do this clunkily by putting a special handler for
POST requests in a view on the container, but it'd be nicer to be able
to do something like:
class Post(grok.Post):
grok.context(IMyContainer)
def update(self, data):
pass
def render(self):
pass
It will take a bit of work to make this happen though, as I don't think
the zope 3 publisher supports doing this out of the box. We'll have to
to make some code that looks for this Post thing first, and if it's not
there, fall back on the normal view behavior. This has some performance
implications, however (an extra view lookup).
Ruby on Rails goes a step further in automating this by actually
providing default behavior for the various handlers. This would be cool
but probably would be in a framework on top of Grok for now, not in the
core.
Comments? Ideas? Eager Zope 3 publisher hackers volunteering to start
building this? :)
Regards,
Martijn
More information about the Grok-dev
mailing list