[CalendarServer-changes] [1427] CalendarServer/branches/users/cdaboo/fast-multiget-1425/ twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Tue Mar 27 13:38:58 PDT 2007


Revision: 1427
          http://trac.macosforge.org/projects/calendarserver/changeset/1427
Author:   cdaboo at apple.com
Date:     2007-03-27 13:38:58 -0700 (Tue, 27 Mar 2007)

Log Message:
-----------
Refactor multiget to use the fast-acl procedure for privilege calculation. Also tweak SQL use to do a batch query.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/fast-multiget-1425/twistedcaldav/extensions.py
    CalendarServer/branches/users/cdaboo/fast-multiget-1425/twistedcaldav/index.py
    CalendarServer/branches/users/cdaboo/fast-multiget-1425/twistedcaldav/method/report_multiget.py

Modified: CalendarServer/branches/users/cdaboo/fast-multiget-1425/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/users/cdaboo/fast-multiget-1425/twistedcaldav/extensions.py	2007-03-27 15:43:56 UTC (rev 1426)
+++ CalendarServer/branches/users/cdaboo/fast-multiget-1425/twistedcaldav/extensions.py	2007-03-27 20:38:58 UTC (rev 1427)
@@ -44,6 +44,7 @@
 from twisted.web2.dav.static import DAVFile as SuperDAVFile
 from twisted.web2.dav.resource import DAVResource as SuperDAVResource
 from twisted.web2.dav.resource import DAVPrincipalResource as SuperDAVPrincipalResource
+from twisted.web2.dav.util import joinURL
 from twistedcaldav.directory.sudo import SudoDirectoryService
 
 
@@ -74,7 +75,158 @@
     Extended L{twisted.web2.dav.resource.DAVResource} implementation.
     """
 
+    def findChildrenFaster(self, depth, request, okcallback, badcallback, names, privileges, inherited_aces):
+        """
+        See L{IDAVResource.findChildren}.
 
+        This implementation works for C{depth} values of C{"0"}, C{"1"}, 
+        and C{"infinity"}.  As long as C{self.listChildren} is implemented
+        """
+        assert depth in ("0", "1", "infinity"), "Invalid depth: %s" % (depth,)
+
+        if depth == "0" or not self.isCollection():
+            yield None
+
+        # First find all depth 1 children
+        #children = []
+        #d = waitForDeferred(self.findChildren("1", request, lambda x, y: children.append((x, y)), privileges=None, inherited_aces=None))
+        #yield d
+        #d.getResult()
+
+        children = []
+        basepath = request.urlForResource(self)
+        childnames = list(self.listChildren())
+        for childname in childnames:
+            if names and childname not in names:
+                continue
+            childpath = joinURL(basepath, childname)
+            d = waitForDeferred(request.locateChildResource(self, childname))
+            yield d
+            child = d.getResult()
+            if child is None:
+                children.append((None, childpath + "/"))
+            else:
+                if child.isCollection():
+                    children.append((child, childpath + "/"))
+                else:
+                    children.append((child, childpath))
+
+        # Generate (acl,supported_privs) map
+        aclmap = {}
+        for resource, url in children:
+            acl = waitForDeferred(resource.accessControlList(request, inheritance=False, inherited_aces=inherited_aces))
+            yield acl
+            acl = acl.getResult()
+            supportedPrivs = waitForDeferred(resource.supportedPrivileges(request))
+            yield supportedPrivs
+            supportedPrivs = supportedPrivs.getResult()
+            aclmap.setdefault((str(acl), str(supportedPrivs)), (acl, supportedPrivs, []))[2].append((resource, url))           
+
+        # Now determine whether each ace satisfies privileges
+        #print aclmap
+        allowed_collections = []
+        for items in aclmap.itervalues():
+            checked = waitForDeferred(self.checkACLPrivilege(request, items[0], items[1], privileges, inherited_aces))
+            yield checked
+            checked = checked.getResult()
+            if checked:
+                for resource, url in items[2]:
+                    okcallback(resource, url)
+                    if resource.isCollection():
+                        allowed_collections.append((resource, url))
+            else:
+                for resource, url in items[2]:
+                    badcallback(resource, url)
+
+        # TODO: Depth: inifinity support
+        if depth == "infinity":
+            for collection, url in allowed_collections:
+                d = waitForDeferred(collection.findChildrenFaster(request, depth, okcallback, badcallback, names, privileges, inherited_aces=None))
+                yield d
+                d.getResult()
+                
+        yield None
+  
+    findChildrenFaster = deferredGenerator(findChildrenFaster)
+
+    def checkACLPrivilege(self, request, acl, privyset, privileges, inherited_aces):
+        
+        principal = self.currentPrincipal(request)
+
+        # Other principal types don't make sense as actors.
+        assert (
+            principal.children[0].name in ("unauthenticated", "href"),
+            "Principal is not an actor: %r" % (principal,)
+        )
+
+        acl = self.fullAccessControlList(acl, inherited_aces)
+
+        pending = list(privileges)
+        denied = []
+
+        for ace in acl.children:
+            for privilege in tuple(pending):
+                if not self.matchPrivilege(davxml.Privilege(privilege), ace.privileges, privyset):
+                    continue
+
+                match = waitForDeferred(self.matchPrincipal(principal, ace.principal, request))
+                yield match
+                match = match.getResult()
+
+                if match:
+                    if ace.invert:
+                        continue
+                else:
+                    if not ace.invert:
+                        continue
+
+                pending.remove(privilege)
+
+                if not ace.allow:
+                    denied.append(privilege)
+
+        yield len(denied) + len(pending) == 0
+
+    checkACLPrivilege = deferredGenerator(checkACLPrivilege)
+
+    def fullAccessControlList(self, acl, inherited_aces):
+        """
+        See L{IDAVResource.accessControlList}.
+
+        This implementation looks up the ACL in the private property
+        C{(L{twisted_private_namespace}, "acl")}.
+        If no ACL has been stored for this resource, it returns the value
+        returned by C{defaultAccessControlList}.
+        If access is disabled it will return C{None}.
+        """
+        #
+        # Inheritance is problematic. Here is what we do:
+        #
+        # 1. A private element <Twisted:inheritable> is defined for use inside
+        #    of a <DAV:ace>. This private element is removed when the ACE is
+        #    exposed via WebDAV.
+        #
+        # 2. When checking ACLs with inheritance resolution, the server must
+        #    examine all parent resources of the current one looking for any
+        #    <Twisted:inheritable> elements.
+        #
+        # If those are defined, the relevant ace is applied to the ACL on the
+        # current resource.
+        #
+
+        # Dynamically update privileges for those ace's that are inherited.
+        if acl:
+            aces = list(acl.children)
+        else:
+            aces = []
+
+        aces.extend(inherited_aces)
+
+        acl = davxml.ACL(*aces)
+
+        return acl
+    
+
 class DAVPrincipalResource (SuperDAVPrincipalResource):
     """
     Extended L{twisted.web2.dav.static.DAVFile} implementation.

Modified: CalendarServer/branches/users/cdaboo/fast-multiget-1425/twistedcaldav/index.py
===================================================================
--- CalendarServer/branches/users/cdaboo/fast-multiget-1425/twistedcaldav/index.py	2007-03-27 15:43:56 UTC (rev 1426)
+++ CalendarServer/branches/users/cdaboo/fast-multiget-1425/twistedcaldav/index.py	2007-03-27 20:38:58 UTC (rev 1427)
@@ -218,6 +218,21 @@
         uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
         return uid is not None
     
+    def resourcesExist(self, names):
+        """
+        Determines whether the specified resource name exists in the index.
+        @param names: a C{list} containing the names of the resources to test
+        @return: a C{list} of all names that exist
+        """
+        statement = "select NAME from RESOURCE where NAME in ("
+        for ctr, ignore_name in enumerate(names):
+            if ctr != 0:
+                statement += ", "
+            statement += ":%s" % (ctr,)
+        statement += ")"
+        results = self._db_values_for_sql(statement, *names)
+        return results
+    
     def search(self, filter):
         """
         Finds resources matching the given qualifiers.

Modified: CalendarServer/branches/users/cdaboo/fast-multiget-1425/twistedcaldav/method/report_multiget.py
===================================================================
--- CalendarServer/branches/users/cdaboo/fast-multiget-1425/twistedcaldav/method/report_multiget.py	2007-03-27 15:43:56 UTC (rev 1426)
+++ CalendarServer/branches/users/cdaboo/fast-multiget-1425/twistedcaldav/method/report_multiget.py	2007-03-27 20:38:58 UTC (rev 1427)
@@ -28,6 +28,7 @@
 from twisted.web2.dav import davxml
 from twisted.web2.dav.element.base import dav_namespace
 from twisted.web2.dav.http import ErrorResponse, MultiStatusResponse
+from twisted.web2.dav.util import joinURL
 from twisted.web2.http import HTTPError, StatusResponse
 
 from twistedcaldav.caldavxml import caldav_namespace
@@ -121,101 +122,144 @@
         filteredaces = None
 
     if not disabled:
-        for href in resources:
-
-            resource_uri = unquote(str(href))
-
-            # Do href checks
-            if requestURIis == "calendar":
-                # Verify that href is an immediate child of the request URI and that resource exists.
+        
+        def doCalendarResponse():
+            # Verify that requested resources are immediate children of the request-URI
+            valid_names = []
+            for href in resources:
+                resource_uri = unquote(str(href))
                 name = resource_uri[resource_uri.rfind("/") + 1:]
                 if not self._isChildURI(request, resource_uri) or self.getChild(name) is None:
                     responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
-                    continue
-                
-                # Verify that we are dealing with a calendar object resource
-                if not self.index().resourceExists(name):
+                else:
+                    valid_names.append(name)
+            
+            # Verify that valid requested resources are calendar objects
+            exists_names = self.index().resourcesExist(valid_names)
+            checked_names = []
+            for name in valid_names:
+                href = joinURL(request.uri, name)
+                if name not in exists_names:
                     responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_ALLOWED)))
-                    continue
-                
-                child = waitForDeferred(request.locateResource(resource_uri))
-                yield child
-                child = child.getResult()
+                else:
+                    checked_names.append(name)
+            
+            # Now determine which valid resources are readable and which are not
+            ok_resources = []
+            bad_resources = []
+            d = self.findChildrenFaster(
+                "1",
+                request,
+                lambda x, y: ok_resources.append((x, y)),
+                lambda x, y: bad_resources.append((x, y)),
+                checked_names,
+                (davxml.Read(),),
+                inherited_aces=filteredaces
+            )
+            x = waitForDeferred(d)
+            yield x
+            x.getResult()
+
+            # Get properties for all valid readable resources
+            for resource, href in ok_resources:
+                d = waitForDeferred(report_common.responseForHref(request, responses, href, resource, None, propertiesForResource, propertyreq))
+                yield d
+                d.getResult()
     
-            elif requestURIis == "collection":
-                name = resource_uri[resource_uri.rfind("/") + 1:]
-                if not self._isChildURI(request, resource_uri, False):
-                    responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
-                    continue
- 
-                child = waitForDeferred(request.locateResource(resource_uri))
-                yield child
-                child = child.getResult()
+            # Indicate error for all valid non-readable resources
+            for ignore_resource, href in bad_resources:
+                responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_ALLOWED)))
+    
+        doCalendarResponse = deferredGenerator(doCalendarResponse)
 
-                if not child or not child.exists():
-                    responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
-                    continue
-
-                parent = waitForDeferred(child.locateParent(request, resource_uri))
-                yield parent
-                parent = parent.getResult()
-
-                if not parent.isCalendarCollection() or not parent.index().resourceExists(name):
-                    responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_ALLOWED)))
-                    continue
+        if requestURIis == "calendar":
+            d = waitForDeferred(doCalendarResponse())
+            yield d
+            d.getResult()
+        else:
+            for href in resources:
+    
+                resource_uri = unquote(str(href))
+    
+                # Do href checks
+                if requestURIis == "calendar":
+                    pass
+        
+                # TODO: we can optimize this one in a similar manner to the calendar case
+                elif requestURIis == "collection":
+                    name = resource_uri[resource_uri.rfind("/") + 1:]
+                    if not self._isChildURI(request, resource_uri, False):
+                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
+                        continue
+     
+                    child = waitForDeferred(request.locateResource(resource_uri))
+                    yield child
+                    child = child.getResult()
+    
+                    if not child or not child.exists():
+                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
+                        continue
+    
+                    parent = waitForDeferred(child.locateParent(request, resource_uri))
+                    yield parent
+                    parent = parent.getResult()
+    
+                    if not parent.isCalendarCollection() or not parent.index().resourceExists(name):
+                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_ALLOWED)))
+                        continue
+                    
+                    # Check privileges on parent - must have at least DAV:read
+                    try:
+                        d = waitForDeferred(parent.checkPrivileges(request, (davxml.Read(),)))
+                        yield d
+                        d.getResult()
+                    except:
+                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_ALLOWED)))
+                        continue
+                    
+                    # Cache the last parent's inherited aces for checkPrivileges optimization
+                    if lastParent != parent:
+                        lastParent = parent
                 
-                # Check privileges on parent - must have at least DAV:read
-                try:
-                    d = waitForDeferred(parent.checkPrivileges(request, (davxml.Read(),)))
-                    yield d
-                    d.getResult()
-                except:
-                    responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_ALLOWED)))
-                    continue
-                
-                # Cache the last parent's inherited aces for checkPrivileges optimization
-                if lastParent != parent:
-                    lastParent = parent
+                        # Do some optimisation of access control calculation by determining any inherited ACLs outside of
+                        # the child resource loop and supply those to the checkPrivileges on each child.
+                        filteredaces = waitForDeferred(parent.inheritedACEsforChildren(request))
+                        yield filteredaces
+                        filteredaces = filteredaces.getResult()
+        
+                else:
+                    name = resource_uri[resource_uri.rfind("/") + 1:]
+                    if (resource_uri != request.uri) or not self.exists():
+                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
+                        continue
+    
+                    parent = waitForDeferred(self.locateParent(request, resource_uri))
+                    yield parent
+                    parent = parent.getResult()
+    
+                    if not parent.isPseudoCalendarCollection() or not parent.index().resourceExists(name):
+                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_ALLOWED)))
+                        continue
+                    child = self
             
                     # Do some optimisation of access control calculation by determining any inherited ACLs outside of
                     # the child resource loop and supply those to the checkPrivileges on each child.
                     filteredaces = waitForDeferred(parent.inheritedACEsforChildren(request))
                     yield filteredaces
                     filteredaces = filteredaces.getResult()
-    
-            else:
-                name = resource_uri[resource_uri.rfind("/") + 1:]
-                if (resource_uri != request.uri) or not self.exists():
-                    responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
-                    continue
-
-                parent = waitForDeferred(self.locateParent(request, resource_uri))
-                yield parent
-                parent = parent.getResult()
-
-                if not parent.isPseudoCalendarCollection() or not parent.index().resourceExists(name):
+        
+                # Check privileges - must have at least DAV:read
+                try:
+                    d = waitForDeferred(child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces))
+                    yield d
+                    d.getResult()
+                except:
                     responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_ALLOWED)))
                     continue
-                child = self
         
-                # Do some optimisation of access control calculation by determining any inherited ACLs outside of
-                # the child resource loop and supply those to the checkPrivileges on each child.
-                filteredaces = waitForDeferred(parent.inheritedACEsforChildren(request))
-                yield filteredaces
-                filteredaces = filteredaces.getResult()
-    
-            # Check privileges - must have at least DAV:read
-            try:
-                d = waitForDeferred(child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces))
+                d = waitForDeferred(report_common.responseForHref(request, responses, href, child, None, propertiesForResource, propertyreq))
                 yield d
                 d.getResult()
-            except:
-                responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_ALLOWED)))
-                continue
-    
-            d = waitForDeferred(report_common.responseForHref(request, responses, href, child, None, propertiesForResource, propertyreq))
-            yield d
-            d.getResult()
 
     yield MultiStatusResponse(responses)
 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20070327/e052f5ed/attachment.html


More information about the calendarserver-changes mailing list