[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