[Checkins] SVN: zope.webdav/trunk/src/zope/webdav/ Improve the handling of Unauthorized exceptions to be

Michael Kerrin michael.kerrin at openapp.biz
Tue Aug 29 14:04:41 EDT 2006


Log message for revision 69877:
  Improve the handling of Unauthorized exceptions to be
  more in line with the WebDAV specification.
  

Changed:
  U   zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py
  U   zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml
  U   zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py
  U   zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py
  U   zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py
  U   zope.webdav/trunk/src/zope/webdav/propfind.py
  U   zope.webdav/trunk/src/zope/webdav/proppatch.py
  U   zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py
  U   zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py

-=-
Modified: zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py	2006-08-29 17:50:32 UTC (rev 69876)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/__init__.py	2006-08-29 18:04:40 UTC (rev 69877)
@@ -68,6 +68,10 @@
 class AlreadyLockedError(DAVError):
     status = 423
 
+
+class UnauthorizedError(DAVError):
+    status = 401
+
 ################################################################################
 #
 # Multi-status error view

Modified: zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml	2006-08-29 17:50:32 UTC (rev 69876)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/configure.zcml	2006-08-29 18:04:40 UTC (rev 69877)
@@ -46,6 +46,13 @@
      provides="zope.webdav.interfaces.IDAVErrorWidget"
      />
 
+  <adapter
+     factory="zope.webdav.exceptions.UnauthorizedError"
+     for="zope.security.interfaces.IUnauthorized
+          zope.webdav.interfaces.IWebDAVRequest"
+     provides="zope.webdav.interfaces.IDAVErrorWidget"
+     />
+
   <!--
       Some default errors that can make it back to the publisher.
     -->

Modified: zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py	2006-08-29 17:50:32 UTC (rev 69876)
+++ zope.webdav/trunk/src/zope/webdav/exceptions/tests/test_simpleviews.py	2006-08-29 18:04:40 UTC (rev 69877)
@@ -136,7 +136,15 @@
         self.assertEqual(errorview.propstatdescription, "")
         self.assertEqual(errorview.responsedescription, "")
 
+    def test_unauthorized_error(self):
+        errorview = zope.webdav.exceptions.UnauthorizedError(None, None)
 
+        self.assertEqual(errorview.status, 401)
+        self.assertEqual(errorview.errors, [])
+        self.assertEqual(errorview.propstatdescription, "")
+        self.assertEqual(errorview.responsedescription, "")
+
+
 class DummyTemplate(object):
 
     def __init__(self, context):

Modified: zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py	2006-08-29 17:50:32 UTC (rev 69876)
+++ zope.webdav/trunk/src/zope/webdav/ftests/test_propfind.py	2006-08-29 18:04:40 UTC (rev 69877)
@@ -25,8 +25,32 @@
 from zope import component
 import zope.webdav.interfaces
 
+from zope.webdav.tests.test_proppatch import unauthProperty, \
+     UnauthorizedPropertyStorage, IUnauthorizedPropertyStorage
+
 class PROPFINDTests(dav.DAVTestCase):
 
+    def setUp(self):
+        super(PROPFINDTests, self).setUp()
+
+        gsm = component.getGlobalSiteManager()
+        gsm.registerUtility(unauthProperty, name = "{DAVtest:}unauthprop")
+        unauthProperty.restricted = True
+        gsm.registerAdapter(UnauthorizedPropertyStorage,
+                            (dav.IResource,
+                             zope.webdav.interfaces.IWebDAVRequest),
+                            provided = IUnauthorizedPropertyStorage)
+
+    def tearDown(self):
+        super(PROPFINDTests, self).tearDown()
+
+        gsm = component.getGlobalSiteManager()
+        gsm.unregisterUtility(unauthProperty, name = "{DAVtest:}unauthprop")
+        gsm.unregisterAdapter(UnauthorizedPropertyStorage,
+                              (dav.IResource,
+                               zope.webdav.interfaces.IWebDAVRequest),
+                              provided = IUnauthorizedPropertyStorage)
+
     def test_badcontent(self):
         response = self.publish("/", env = {"REQUEST_METHOD": "PROPFIND"},
                                 request_body = "some content",
@@ -449,6 +473,41 @@
         self.assertMSPropertyValue(response, "{DAVtest:}exampletextprop",
                                    text_value = "EXAMPLE TEXT PROP")
 
+    def test_allprop_with_include_on_unauthorized(self):
+        file = self.addResource("/r", "some content", title = "Test Resource")
+
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<propfind xmlns:D="DAV:" xmlns="DAV:">
+  <D:allprop />
+  <D:include>
+    <Dtest:unauthprop xmlns:Dtest="DAVtest:" />
+  </D:include>
+</propfind>"""
+
+        httpresponse, xmlbody = self.checkPropfind(
+            "/r", env = {"DEPTH": "0", "CONTENT_TYPE": "application/xml"},
+            properties = """<D:allprop />
+<D:include>
+  <Dtest:unauthprop xmlns:Dtest="DAVtest:" />
+</D:include>
+""")
+
+        responses = xmlbody.findall("{DAV:}response")
+        self.assertEqual(len(responses), 1)
+        response = responses[0]
+
+        hrefs = response.findall("{DAV:}href")
+        self.assertEqual(len(hrefs), 1)
+        self.assertEqual(hrefs[0].text, "http://localhost/r")
+
+        propstats = response.findall("{DAV:}propstat")
+        self.assertEqual(len(propstats), 2)
+        props = propstats[0].findall("{DAV:}prop")
+        self.assertEqual(len(props), 1)
+
+        self.assertMSPropertyValue(response, "{DAVtest:}unauthprop",
+                                   status = 401)
+
     def test_propfind_onfile(self):
         self.addFile("/testfile", "some file content", "text/plain")
         httpresponse, xmlbody = self.checkPropfind(

Modified: zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py	2006-08-29 17:50:32 UTC (rev 69876)
+++ zope.webdav/trunk/src/zope/webdav/ftests/test_proppatch.py	2006-08-29 18:04:40 UTC (rev 69877)
@@ -21,6 +21,7 @@
 import transaction
 
 from zope import component
+from zope.security.interfaces import Unauthorized
 
 import dav
 
@@ -53,6 +54,27 @@
         self.assertEqual(response.getStatus(), 422)
         self.assertEqual(response.getBody(), "")
 
+    def test_setdisplayname_unauthorized(self):
+        file = self.addResource("/r", "some content", "Test Resource")
+
+        self.assertEqual(self.getRootFolder()["r"].title, "Test Resource")
+
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propertyupdate xmlns:D="DAV:" xmlns="DAV:">
+  <D:set><D:prop>
+    <D:displayname>Test File</D:displayname>
+  </D:prop></D:set>
+</D:propertyupdate>"""
+
+        response = self.publish("/r", env = {"REQUEST_METHOD": "PROPPATCH",
+                                             "CONTENT_TYPE": "application/xml",
+                                             "CONTENT_LENGTH": len(body)},
+                                request_body = body,
+                                handle_errors = True)
+
+        # we need to be logged in to set the DAV:displayname property.
+        self.assertEqual(response.getStatus(), 401)
+
     def test_setdisplayname(self):
         set_properties = "<D:displayname>Test File</D:displayname>"
         file = self.addResource("/r", "some content", "Test Resource")

Modified: zope.webdav/trunk/src/zope/webdav/propfind.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/propfind.py	2006-08-29 17:50:32 UTC (rev 69876)
+++ zope.webdav/trunk/src/zope/webdav/propfind.py	2006-08-29 18:04:40 UTC (rev 69877)
@@ -42,6 +42,7 @@
 from zope import component
 from zope.app.container.interfaces import IReadContainer
 from zope.app.error.interfaces import IErrorReportingUtility
+from zope.security.interfaces import Unauthorized
 
 from zope.etree.interfaces import IEtree
 import zope.webdav.utils
@@ -145,7 +146,32 @@
 
         return responses
 
+    def handleException(self, proptag, exc_info, request, response):
+        error_view = component.queryMultiAdapter(
+            (exc_info[1], request), zope.webdav.interfaces.IDAVErrorWidget)
+        if error_view is None:
+            ## An unexpected error occured here. This errr should be
+            ## fixed. In order to easily debug the problem we will
+            ## log the error with the ErrorReportingUtility
+            errUtility = component.getUtility(IErrorReportingUtility)
+            errUtility.raising(exc_info, request)
+            propstat = response.getPropstat(500) # Internal Server Error
+        else:
+            propstat = response.getPropstat(error_view.status)
+            ## XXX - needs testing
+            propstat.responsedescription += error_view.propstatdescription
+            response.responsedescription += error_view.responsedescription
+
+        etree = component.getUtility(IEtree)
+        propstat.properties.append(etree.Element(proptag))
+
     def renderPropnames(self, ob, req, ignore):
+        """
+        See doc string for the renderAllProperties method. Note that we don't
+        need to worry about the security in this method has the permissions on
+        the storage adapters should be enough to hide any properties that users
+        don't have permission to see.
+        """
         response = zope.webdav.utils.Response(
             zope.webdav.utils.getObjectURL(ob, req))
 
@@ -160,19 +186,49 @@
         return response
 
     def renderAllProperties(self, ob, req, include):
+        """
+        The specification says:
+        
+          Properties may be subject to access control.  In the case of
+          'allprop' and 'propname' requests, if a principal does not have the
+          right to know whether a particular property exists then the property
+          MAY be silently excluded from the response.
+
+        """
         response = zope.webdav.utils.Response(
             zope.webdav.utils.getObjectURL(ob, req))
 
         for davprop, adapter in \
                 zope.webdav.properties.getAllProperties(ob, req):
-            if davprop.restricted:
-                if include is None or \
-                       include.find("{%s}%s" %(davprop.namespace,
-                                               davprop.__name__)) is None:
-                    continue
+            isIncluded = False
+            if include is not None and \
+                   include.find("{%s}%s" %(davprop.namespace,
+                                           davprop.__name__)) is not None:
+                isIncluded = True
+            elif davprop.restricted:
+                continue
 
-            davwidget = zope.webdav.properties.getWidget(davprop, adapter, req)
-            response.addProperty(200, davwidget.render())
+            try:
+                # getWidget and render are two possible areas where the
+                # property is silently ignored because of security concerns.
+                davwidget = zope.webdav.properties.getWidget(
+                    davprop, adapter, req)
+                response.addProperty(200, davwidget.render())
+            except Unauthorized:
+                if isIncluded:
+                    self.handleException(
+                        "{%s}%s" %(davprop.namespace, davprop.__name__),
+                        sys.exc_info(), req,
+                        response)
+                # Users don't have the permission to view this property and
+                # since they didn't explicitly ask for the named property
+                # we will silently ignore this property.
+                pass
+            except Exception:
+                self.handleException(
+                    "{%s}%s" %(davprop.namespace, davprop.__name__),
+                    sys.exc_info(), req,
+                    response)
 
         return response
 
@@ -180,8 +236,6 @@
         response = zope.webdav.utils.Response(
             zope.webdav.utils.getObjectURL(ob, req))
 
-        etree = component.getUtility(IEtree)
-
         for prop in props:
             try:
                 davprop, adapter = zope.webdav.properties.getProperty(
@@ -189,29 +243,8 @@
                 davwidget = zope.webdav.properties.getWidget(
                     davprop, adapter, req)
                 propstat = response.getPropstat(200)
-                rendered_el = davwidget.render()
-            except Exception, error:
-                exc_info = sys.exc_info()
+                propstat.properties.append(davwidget.render())
+            except Exception:
+                self.handleException(prop.tag, sys.exc_info(), req, response)
 
-                error_view = component.queryMultiAdapter(
-                    (error, req), zope.webdav.interfaces.IDAVErrorWidget)
-                if error_view is None:
-                    ## An unexpected error occured here. This errr should be
-                    ## fixed. In order to easily debug the problem we will
-                    ## log the error with the ErrorReportingUtility
-                    errUtility = component.getUtility(IErrorReportingUtility)
-                    errUtility.raising(exc_info, req)
-                    propstat = response.getPropstat(500) # Internal Server Error
-                else:
-                    propstat = response.getPropstat(error_view.status)
-                    ## XXX - needs testing
-                    propstat.responsedescription += \
-                                                 error_view.propstatdescription
-                    response.responsedescription += \
-                                                 error_view.responsedescription
-
-                rendered_el = etree.Element(prop.tag)
-
-            propstat.properties.append(rendered_el)
-
         return response

Modified: zope.webdav/trunk/src/zope/webdav/proppatch.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/proppatch.py	2006-08-29 17:50:32 UTC (rev 69876)
+++ zope.webdav/trunk/src/zope/webdav/proppatch.py	2006-08-29 18:04:40 UTC (rev 69877)
@@ -27,6 +27,7 @@
 import zope.webdav.interfaces
 import zope.webdav.properties
 from zope.etree.interfaces import IEtree
+from zope.security.interfaces import Unauthorized
 
 
 class PROPPATCH(object):
@@ -76,6 +77,11 @@
                         self.handleSet(prop)
                     else:
                         self.handleRemove(prop)
+                except Unauthorized:
+                    # If the use doesn't have the correct permission to modify
+                    # a property then we need to re-raise the Unauthorized
+                    # exception in order to ask the user to log in.
+                    raise
                 except Exception, error:
                     isError = True
                     propErrors.append((prop.tag, error))

Modified: zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py	2006-08-29 17:50:32 UTC (rev 69876)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_propfind.py	2006-08-29 18:04:40 UTC (rev 69877)
@@ -30,6 +30,7 @@
 from zope.traversing.browser.interfaces import IAbsoluteURL
 from zope.app.container.interfaces import IReadContainer
 from zope.app.error.interfaces import IErrorReportingUtility
+from zope.security.interfaces import IUnauthorized
 
 import zope.webdav.properties
 import zope.webdav.publisher
@@ -40,6 +41,8 @@
 from zope.etree.testing import etreeSetup, etreeTearDown, assertXMLEqual
 from zope.etree.interfaces import IEtree
 
+from test_proppatch import unauthProperty, UnauthorizedPropertyStorage, \
+     IUnauthorizedPropertyStorage
 from utils import TestMultiStatusBody
 
 class TestRequest(zope.webdav.publisher.WebDAVRequest):
@@ -300,6 +303,10 @@
                         name = "{DAV:}resourcetype")
     gsm.registerUtility(brokenProperty, name = "{DAVtest:}brokenprop",
                         provided = zope.webdav.interfaces.IDAVProperty)
+    gsm.registerUtility(unauthProperty, name = "{DAVtest:}unauthprop")
+    # make sure that this property is always restricted so that we
+    # only try and render this property whenever we want to.
+    unauthProperty.restricted = True
 
     gsm.registerAdapter(ExamplePropertyStorage,
                         (IResource, zope.webdav.interfaces.IWebDAVRequest),
@@ -307,6 +314,9 @@
     gsm.registerAdapter(BrokenPropertyStorage,
                         (IResource, zope.webdav.interfaces.IWebDAVRequest),
                         provided = IBrokenPropertyStorage)
+    gsm.registerAdapter(UnauthorizedPropertyStorage,
+                        (IResource, zope.webdav.interfaces.IWebDAVRequest),
+                        provided = IUnauthorizedPropertyStorage)
     gsm.registerAdapter(zope.webdav.coreproperties.ResourceTypeAdapter)
 
     gsm.registerAdapter(DummyResourceURL,
@@ -327,6 +337,9 @@
     gsm.registerAdapter(zope.webdav.exceptions.PropertyNotFoundError,
                         (zope.webdav.interfaces.IPropertyNotFound,
                          zope.webdav.interfaces.IWebDAVRequest))
+    gsm.registerAdapter(zope.webdav.exceptions.UnauthorizedError,
+                        (IUnauthorized,
+                         zope.webdav.interfaces.IWebDAVRequest))
 
 def propfindTearDown():
     etreeTearDown()
@@ -345,7 +358,8 @@
     gsm.unregisterUtility(zope.webdav.coreproperties.resourcetype,
                           name = "{DAV:}resourcetype")
     gsm.unregisterUtility(brokenProperty, name = "{DAVtest:}brokenprop",
-                        provided = zope.webdav.interfaces.IDAVProperty)
+                          provided = zope.webdav.interfaces.IDAVProperty)
+    gsm.unregisterUtility(unauthProperty, name = "{DAVtest:}unauthprop")
 
     gsm.unregisterAdapter(ExamplePropertyStorage,
                           (IResource, zope.webdav.interfaces.IWebDAVRequest),
@@ -353,6 +367,9 @@
     gsm.unregisterAdapter(BrokenPropertyStorage,
                           (IResource, zope.webdav.interfaces.IWebDAVRequest),
                           provided = IBrokenPropertyStorage)
+    gsm.registerAdapter(UnauthorizedPropertyStorage,
+                        (IResource, zope.webdav.interfaces.IWebDAVRequest),
+                        provided = IUnauthorizedPropertyStorage)
     gsm.unregisterAdapter(zope.webdav.coreproperties.ResourceTypeAdapter)
 
     gsm.unregisterAdapter(DummyResourceURL,
@@ -369,6 +386,9 @@
     gsm.unregisterAdapter(zope.webdav.exceptions.PropertyNotFoundError,
                           (zope.webdav.interfaces.IPropertyNotFound,
                            zope.webdav.interfaces.IWebDAVRequest))
+    gsm.unregisterAdapter(zope.webdav.exceptions.UnauthorizedError,
+                          (IUnauthorized,
+                           zope.webdav.interfaces.IWebDAVRequest))
     gsm.unregisterAdapter(zope.webdav.widgets.ListDAVWidget,
                           (zope.schema.interfaces.IList,
                            zope.webdav.interfaces.IWebDAVRequest))
@@ -415,14 +435,16 @@
         self.assertMSPropertyValue(response, "{DAVtest:}exampleintprop")
         self.assertMSPropertyValue(response, "{DAV:}resourcetype")
         self.assertMSPropertyValue(response, "{DAVtest:}brokenprop")
+        self.assertMSPropertyValue(response, "{DAVtest:}unauthprop")
 
         assertXMLEqual(response, """<ns0:response xmlns:ns0="DAV:">
 <ns0:href xmlns:ns0="DAV:">/resource</ns0:href>
 <ns0:propstat xmlns:ns0="DAV:" xmlns:ns01="DAVtest:">
   <ns0:prop xmlns:ns0="DAV:">
-    <ns01:brokenprop xmlns:ns0="DAVtest:"/>
-    <ns01:exampletextprop xmlns:ns0="DAVtest:"/>
-    <ns01:exampleintprop xmlns:ns0="DAVtest:"/>
+    <ns1:brokenprop xmlns:ns1="DAVtest:"/>
+    <ns1:exampletextprop xmlns:ns1="DAVtest:"/>
+    <ns1:exampleintprop xmlns:ns1="DAVtest:"/>
+    <ns1:unauthprop xmlns:ns1="DAVtest:"/>
     <ns0:resourcetype />
   </ns0:prop>
   <ns0:status xmlns:ns0="DAV:">HTTP/1.1 200 OK</ns0:status>
@@ -578,11 +600,66 @@
         error = self.errUtility.errors[0]
         self.assertEqual(isinstance(error[0][1], NotImplementedError), True)
 
+    def test_renderUnauthorizedProperty(self):
+        resource = Resource("some text", 10)
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+        propf = PROPFIND(None, None)
 
+        etree = component.getUtility(IEtree)
+        props = etree.fromstring("""<prop xmlns="DAV:" xmlns:D="DAVtest:">
+<D:unauthprop />
+<D:exampletextprop />
+</prop>""")
+
+        response = propf.renderSelectedProperties(resource, request, props)
+        response = response()
+
+        # The PROPFIND method should return a 401 when the user is unauthorized
+        # to view a property.
+        self.assertMSPropertyValue(response, "{DAVtest:}exampletextprop",
+                                   text_value = "some text")
+        self.assertMSPropertyValue(response, "{DAVtest:}unauthprop",
+                                   status = 401)
+
+        # PROPFIND does catch all exceptions during the main PROPFIND method
+        # but instead we need to make sure that the renderSelectedProperties
+        # does throw the exception.
+
+    def test_renderAllProperties_unauthorized(self):
+        resource = Resource("some text", 10)
+        request = zope.webdav.publisher.WebDAVRequest(StringIO(""), {})
+        propf = PROPFIND(None, request)
+
+        # Set the unauthproperty as un-restricted so that the
+        # renderAllProperties will render all the properties.
+        unauthProperty.restricted = False
+
+        # PROPFIND does catch all exceptions during the main PROPFIND method
+        # but instead we need to make sure that the renderSelectedProperties
+        # does throw the exception.
+        response = propf.renderAllProperties(resource, request, None)
+        response = response()
+
+        self.assertEqual(len(response.findall("{DAV:}propstat")), 1)
+        self.assertEqual(len(response.findall("{DAV:}propstat/{DAV:}prop")), 1)
+
+        foundUnauthProp = False
+        for prop in response.findall("{DAV:}propstat/{DAV:}prop")[0]:
+            if prop.tag == "{DAVtest:}unauthprop":
+                foundUnauthProp = True
+
+        self.assert_(not foundUnauthProp,
+                     "The unauthprop should not be included in the all " \
+                     "property response since it has security restrictions.")
+
+
 class PROPFINDRecuseTest(unittest.TestCase):
 
     def setUp(self):
         propfindSetUp()
+        # make sure the unauthProperty is restricted has otherwise it will
+        # break all the renderAllProperties methods.
+        unauthProperty.restricted = True
 
     def tearDown(self):
         propfindTearDown()

Modified: zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py
===================================================================
--- zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py	2006-08-29 17:50:32 UTC (rev 69876)
+++ zope.webdav/trunk/src/zope/webdav/tests/test_proppatch.py	2006-08-29 18:04:40 UTC (rev 69877)
@@ -27,6 +27,7 @@
 from zope import schema
 import zope.schema.interfaces
 from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.security.interfaces import Unauthorized
 
 import zope.webdav.proppatch
 import zope.webdav.publisher
@@ -344,13 +345,22 @@
     extratextprop = schema.Text(
         title = u"Property with no storage")
 
+class IUnauthorizedPropertyStorage(interface.Interface):
+
+    unauthprop = schema.TextLine(
+        title = u"Property that you are not allowed to set")
+
 exampleIntProperty = zope.webdav.properties.DAVProperty(
     "{DAVtest:}exampleintprop", IExamplePropertyStorage)
 exampleTextProperty = zope.webdav.properties.DAVProperty(
     "{DAVtest:}exampletextprop", IExamplePropertyStorage)
 extraTextProperty = zope.webdav.properties.DAVProperty(
     "{DAVtest:}extratextprop", IExtraPropertyStorage)
+unauthProperty = zope.webdav.properties.DAVProperty(
+    "{DAVtest:}unauthprop", IUnauthorizedPropertyStorage)
+unauthProperty.restricted = True
 
+
 class ExamplePropertyStorage(object):
     interface.implements(IExamplePropertyStorage)
 
@@ -370,6 +380,21 @@
     exampletextprop = _getproperty("text", default = u"")
 
 
+class UnauthorizedPropertyStorage(object):
+    interface.implements(IUnauthorizedPropertyStorage)
+
+    def __init__(self, context, request):
+        pass
+
+    @apply
+    def unauthprop():
+        def get(self):
+            raise Unauthorized("You are not allowed to read this property")
+        def set(self, value):
+            raise Unauthorized("You are not allowed to set this property!")
+        return property(get, set)
+
+
 class PROPPATCHHandlePropertyModification(unittest.TestCase):
 
     def setUp(self):
@@ -387,10 +412,14 @@
         gsm.registerUtility(extraTextProperty,
                             name = "{DAVtest:}extratextprop",
                             provided = zope.webdav.interfaces.IDAVProperty)
+        gsm.registerUtility(unauthProperty, name = "{DAVtest:}unauthprop")
 
         gsm.registerAdapter(ExamplePropertyStorage,
                             (IResource, zope.webdav.interfaces.IWebDAVRequest),
                             provided = IExamplePropertyStorage)
+        gsm.registerAdapter(UnauthorizedPropertyStorage,
+                            (IResource, zope.webdav.interfaces.IWebDAVRequest),
+                            provided = IUnauthorizedPropertyStorage)
 
         gsm.registerAdapter(zope.webdav.widgets.TextDAVInputWidget,
                             (zope.schema.interfaces.IText,
@@ -410,11 +439,16 @@
         gsm.unregisterUtility(extraTextProperty,
                               name = "{DAVtest:}extratextprop",
                               provided = zope.webdav.interfaces.IDAVProperty)
+        gsm.unregisterUtility(unauthProperty, name = "{DAVtest:}unauthprop")
 
         gsm.unregisterAdapter(ExamplePropertyStorage,
                               (IResource,
                                zope.webdav.interfaces.IWebDAVRequest),
                               provided = IExamplePropertyStorage)
+        gsm.unregisterAdapter(UnauthorizedPropertyStorage,
+                              (IResource,
+                               zope.webdav.interfaces.IWebDAVRequest),
+                              provided = IUnauthorizedPropertyStorage)
 
         gsm.unregisterAdapter(zope.webdav.widgets.TextDAVInputWidget,
                               (zope.schema.interfaces.IText,
@@ -450,6 +484,18 @@
                           propp.handleSet,
                           propel)
 
+    def test_handleSet_unauthorized(self):
+        etree = component.getUtility(IEtree)
+        propel = etree.Element("{DAVtest:}unauthprop")
+        propel.text = "Example Text Prop"
+
+        request = TestRequest(
+            set_properties = """<Dt:unauthprop xmlns:Dt="DAVtest:">Example Text Prop</Dt:unauthprop>""")
+        resource = Resource("Text Prop", 10)
+
+        propp = zope.webdav.proppatch.PROPPATCH(resource, request)
+        self.assertRaises(Unauthorized, propp.handleSet, propel)
+
     def test_handleSet_property_notfound(self):
         etree = component.getUtility(IEtree)
         propel = etree.Element("{DAVtest:}exampletextpropmissing")



More information about the Checkins mailing list