[Checkins] SVN: grok/branches/faassen-rest/src/grok/ Add security tests and massage things until they work. This required

Martijn Faassen faassen at infrae.com
Mon Oct 15 16:38:49 EDT 2007


Log message for revision 80884:
  Add security tests and massage things until they work. This required
  the creation of a new GrokHTTPPublication that is along the lines of
  GrokBrowserPublication and GrokXMLRPCPublication.
  
  Incidentally this also allows the right MethodNotAllowedError to be
  raised for weird verbs like FROG.
  

Changed:
  U   grok/branches/faassen-rest/src/grok/configure.zcml
  U   grok/branches/faassen-rest/src/grok/ftests/rest/rest.py
  U   grok/branches/faassen-rest/src/grok/meta.py
  U   grok/branches/faassen-rest/src/grok/publication.py

-=-
Modified: grok/branches/faassen-rest/src/grok/configure.zcml
===================================================================
--- grok/branches/faassen-rest/src/grok/configure.zcml	2007-10-15 18:33:03 UTC (rev 80883)
+++ grok/branches/faassen-rest/src/grok/configure.zcml	2007-10-15 20:38:49 UTC (rev 80884)
@@ -76,6 +76,14 @@
       priority="11"
       />
 
+  <publisher
+      name="HTTP"
+      factory=".publication.GrokHTTPFactory"
+      methods="*"
+      mimetypes="*"
+      priority="1"
+      />
+
   <!-- need to grok this for some basic REST support -->
   <grok:grok package=".rest" />
 

Modified: grok/branches/faassen-rest/src/grok/ftests/rest/rest.py
===================================================================
--- grok/branches/faassen-rest/src/grok/ftests/rest/rest.py	2007-10-15 18:33:03 UTC (rev 80883)
+++ grok/branches/faassen-rest/src/grok/ftests/rest/rest.py	2007-10-15 20:38:49 UTC (rev 80884)
@@ -141,18 +141,52 @@
   Content-Type: text/plain
   <BLANKLINE>
   Method Not Allowed
-  
+
+We can also try this with a completely made-up request method, like FROG::
+
+  >>> print http('FROG /++rest++b/app HTTP/1.1')
+  HTTP/1. 405 Method Not Allowed
+  Allow: GET, PUT
+  Content-Length: 18
+  <BLANKLINE>
+  Method Not Allowed
+
+Let's now see whether security works properly with REST. GET should
+be public::
+
+  >>> print http('GET /++rest++e/app/alpha HTTP/1.1')
+  HTTP/1. 200 Ok
+  Content-Length: 4
+  Content-Type: text/plain
+  <BLANKLINE>
+  GET3
+
+POST, PUT and DELETE however are not public::
+
+  >>> print http('POST /++rest++e/app/alpha HTTP/1.1')
+  HTTP/1. 401 Unauthorized
+  Content-Length: 0
+  Content-Type: text/plain
+  WWW-Authenticate: basic realm="Zope"
+  <BLANKLINE>
+
+  >>> print http('PUT /++rest++e/app/alpha HTTP/1.1')
+  HTTP/1. 401 Unauthorized
+  Content-Length: 0
+  WWW-Authenticate: basic realm="Zope"
+  <BLANKLINE>
+
+  >>> print http('DELETE /++rest++e/app/alpha HTTP/1.1')
+  HTTP/1. 401 Unauthorized
+  Content-Length: 0
+  WWW-Authenticate: basic realm="Zope"
+  <BLANKLINE>
+
 Todo:
 
 * Support for OPTIONS, HEAD, other methods?
 
 * Content-Type header is there for GET/POST, but not for PUT/DELETE...
-
-* MethodNotAllowed doesn't work correctly if the method is completely
-  unrecognized, such as FROG instead of POST. It falls back on the default
-  MethodNotAllowed then, instead of GrokMethodNotAllowed.
-  
-* Security tests.
 """
 
 import grok
@@ -172,6 +206,9 @@
 class LayerC(grok.IRESTLayer):
     pass
 
+class LayerSecurity(grok.IRESTLayer):
+    pass
+
 class A(grok.RESTProtocol):
     grok.layer(LayerA)
 
@@ -184,6 +221,9 @@
 class D(grok.RESTProtocol):
     grok.layer(grok.IRESTLayer)
 
+class E(grok.RESTProtocol):
+    grok.layer(LayerSecurity)
+    
 class ARest(grok.REST):
     grok.layer(LayerA)
     grok.context(MyApp)
@@ -219,3 +259,24 @@
     
     def GET(self):
         return "GET2"
+
+class SecurityRest(grok.REST):
+    grok.context(MyContent)
+    grok.layer(LayerSecurity)
+    
+    @grok.require('zope.Public')
+    def GET(self):
+        return "GET3"
+
+    @grok.require('zope.ManageContent')
+    def POST(self):
+        return "POST3"
+
+    @grok.require('zope.ManageContent')
+    def PUT(self):
+        return "PUT3"
+
+    @grok.require('zope.ManageContent')
+    def DELETE(self):
+        return "DELETE3"
+    

Modified: grok/branches/faassen-rest/src/grok/meta.py
===================================================================
--- grok/branches/faassen-rest/src/grok/meta.py	2007-10-15 18:33:03 UTC (rev 80883)
+++ grok/branches/faassen-rest/src/grok/meta.py	2007-10-15 20:38:49 UTC (rev 80884)
@@ -128,9 +128,10 @@
         self.context = context
         self.request = request
         self.__parent__ = self.context
-        
-    def browserDefault(self, request):
-        return self, ()
+
+    # XXX evidently not necessary?
+    #def browserDefault(self, request):
+    #    return self, ()
     
 class RESTGrokker(martian.ClassGrokker):
     component_class = grok.REST

Modified: grok/branches/faassen-rest/src/grok/publication.py
===================================================================
--- grok/branches/faassen-rest/src/grok/publication.py	2007-10-15 18:33:03 UTC (rev 80883)
+++ grok/branches/faassen-rest/src/grok/publication.py	2007-10-15 20:38:49 UTC (rev 80884)
@@ -14,15 +14,19 @@
 """Grok publication objects
 """
 
+from grok.components import GrokMethodNotAllowed
+
+from zope import component
 from zope.security.proxy import removeSecurityProxy
 from zope.security.checker import selectChecker
+from zope.publisher.publish import mapply
 
-from zope.app.publication.http import BaseHTTPPublication
+from zope.app.publication.http import BaseHTTPPublication, HTTPPublication
 from zope.app.publication.browser import BrowserPublication
 from zope.app.publication.requestpublicationfactories import \
-     BrowserFactory, XMLRPCFactory
+     BrowserFactory, XMLRPCFactory, HTTPFactory
+from zope.app.http.interfaces import IHTTPException
 
-
 class ZopePublicationSansProxy(object):
 
     def getApplication(self, request):
@@ -59,9 +63,28 @@
 class GrokXMLRPCPublication(ZopePublicationSansProxy, BaseHTTPPublication):
     pass
 
-
 class GrokXMLRPCFactory(XMLRPCFactory):
 
     def __call__(self):
         request, publication = super(GrokXMLRPCFactory, self).__call__()
         return request, GrokXMLRPCPublication
+
+
+class GrokHTTPPublication(ZopePublicationSansProxy, HTTPPublication):
+   def callObject(self, request, ob):
+       orig = ob
+       if not IHTTPException.providedBy(ob):
+           ob = component.queryMultiAdapter((ob, request),
+                                            name=request.method)
+           checker = selectChecker(ob)
+           if checker is not None:
+               checker.check(ob, '__call__')
+           ob = getattr(ob, request.method, None)
+           if ob is None:
+               raise GrokMethodNotAllowed(orig, request)
+       return mapply(ob, request.getPositionalArguments(), request)
+
+class GrokHTTPFactory(HTTPFactory):
+    def __call__(self):
+        request, publication = super(GrokHTTPFactory, self).__call__()
+        return request, GrokHTTPPublication



More information about the Checkins mailing list