[CalendarServer-changes] [3416] CalendarServer/branches/users/wsanchez/deployment
source_changes at macosforge.org
source_changes at macosforge.org
Fri Nov 28 11:45:46 PST 2008
Revision: 3416
http://trac.macosforge.org/projects/calendarserver/changeset/3416
Author: cdaboo at apple.com
Date: 2008-11-28 11:45:46 -0800 (Fri, 28 Nov 2008)
Log Message:
-----------
Merge expand REPORT and expanded proxy principal membership properties to deployment.
Modified Paths:
--------------
CalendarServer/branches/users/wsanchez/deployment/lib-patches/Twisted/twisted.web2.dav.resource.patch
CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/customxml.py
CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/aggregate.py
CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/appleopendirectory.py
CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/calendaruserproxy.py
CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/principal.py
CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_opendirectoryrecords.py
CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_proxyprincipalmembers.py
CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/extensions.py
CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/resource.py
Added Paths:
-----------
CalendarServer/branches/users/wsanchez/deployment/lib-patches/Twisted/twisted.web2.dav.method.report_expand.patch
Added: CalendarServer/branches/users/wsanchez/deployment/lib-patches/Twisted/twisted.web2.dav.method.report_expand.patch
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/lib-patches/Twisted/twisted.web2.dav.method.report_expand.patch (rev 0)
+++ CalendarServer/branches/users/wsanchez/deployment/lib-patches/Twisted/twisted.web2.dav.method.report_expand.patch 2008-11-28 19:45:46 UTC (rev 3416)
@@ -0,0 +1,214 @@
+Index: twisted/web2/dav/method/report_expand.py
+===================================================================
+--- twisted/web2/dav/method/report_expand.py (revision 19773)
++++ twisted/web2/dav/method/report_expand.py (working copy)
+@@ -1,6 +1,6 @@
+ # -*- test-case-name: twisted.web2.dav.test.test_report_expand -*-
+ ##
+-# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
++# Copyright (c) 2005-2008 Apple Computer, Inc. All rights reserved.
+ #
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
+ # of this software and associated documentation files (the "Software"), to deal
+@@ -19,8 +19,6 @@
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ # SOFTWARE.
+-#
+-# DRI: Wilfredo Sanchez, wsanchez at apple.com
+ ##
+
+ """
+@@ -29,86 +27,143 @@
+
+ __all__ = ["report_DAV__expand_property"]
+
++from twisted.internet.defer import inlineCallbacks, returnValue
+ from twisted.python import log
+ from twisted.python.failure import Failure
+-from twisted.internet.defer import deferredGenerator, waitForDeferred
+ from twisted.web2 import responsecode
+ from twisted.web2.dav import davxml
+-from twisted.web2.dav.http import statusForFailure
+ from twisted.web2.dav.davxml import dav_namespace
++from twisted.web2.dav.http import statusForFailure, MultiStatusResponse
++from twisted.web2.dav.method import prop_common
++from twisted.web2.dav.method.propfind import propertyName
++from twisted.web2.dav.resource import AccessDeniedError
++from twisted.web2.dav.util import parentForURL
++from twisted.web2.http import HTTPError, StatusResponse
+
++ at inlineCallbacks
+ def report_DAV__expand_property(self, request, expand_property):
+ """
+ Generate an expand-property REPORT. (RFC 3253, section 3.8)
++
++ TODO: for simplicity we will only support one level of expansion.
+ """
+- # FIXME: Handle depth header
+-
++ # Verify root element
+ if not isinstance(expand_property, davxml.ExpandProperty):
+ raise ValueError("%s expected as root element, not %s."
+ % (davxml.ExpandProperty.sname(), expand_property.sname()))
+
++ # Only handle Depth: 0
++ depth = request.headers.getHeader("depth", "0")
++ if depth != "0":
++ log.err("Non-zero depth is not allowed: %s" % (depth,))
++ raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
++
+ #
+- # Expand DAV:allprop
++ # Get top level properties to expand and make sure we only have one level
+ #
+ properties = {}
+
+ for property in expand_property.children:
+- namespace = property.getAttribute("namespace")
+- name = property.getAttribute("name")
++ namespace = property.attributes.get("namespace", dav_namespace)
++ name = property.attributes.get("name", "")
++
++ # Make sure children have no children
++ props_to_find = []
++ for child in property.children:
++ if child.children:
++ log.err("expand-property REPORT only supports single level expansion")
++ raise HTTPError(StatusResponse(
++ responsecode.NOT_IMPLEMENTED,
++ "expand-property REPORT only supports single level expansion"
++ ))
++ child_namespace = child.attributes.get("namespace", dav_namespace)
++ child_name = child.attributes.get("name", "")
++ props_to_find.append((child_namespace, child_name))
+
+- if not namespace: namespace = dav_namespace
++ properties[(namespace, name)] = props_to_find
+
+- if (namespace, name) == (dav_namespace, "allprop"):
+- all_properties = waitForDeferred(self.listAllProp(request))
+- yield all_properties
+- all_properties = all_properties.getResult()
+-
+- for all_property in all_properties:
+- properties[all_property.qname()] = property
+- else:
+- properties[(namespace, name)] = property
+-
+ #
+- # Look up the requested properties
++ # Generate the expanded responses status for each top-level property
+ #
+ properties_by_status = {
+ responsecode.OK : [],
+ responsecode.NOT_FOUND : [],
+ }
++
++ filteredaces = None
++ lastParent = None
+
+- for property in properties:
+- my_properties = waitForDeferred(self.listProperties(request))
+- yield my_properties
+- my_properties = my_properties.getResult()
++ for qname in properties.iterkeys():
++ try:
++ prop = (yield self.readProperty(qname, request))
++
++ # Form the PROPFIND-style DAV:prop element we need later
++ props_to_return = davxml.PropertyContainer(*properties[qname])
+
+- if property in my_properties:
+- try:
+- value = waitForDeferred(self.readProperty(property, request))
+- yield value
+- value = value.getResult()
++ # Now dereference any HRefs
++ responses = []
++ for href in prop.children:
++ if isinstance(href, davxml.HRef):
++
++ # Locate the Href resource and its parent
++ resource_uri = str(href)
++ child = (yield request.locateResource(resource_uri))
++
++ if not child or not child.exists():
++ responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
++ continue
++ parent = (yield request.locateResource(parentForURL(resource_uri)))
++
++ # Check privileges on parent - must have at least DAV:read
++ try:
++ yield parent.checkPrivileges(request, (davxml.Read(),))
++ except AccessDeniedError:
++ responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
++ 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 = (yield parent.inheritedACEsforChildren(request))
+
+- if isinstance(value, davxml.HRef):
+- raise NotImplementedError()
+- else:
+- raise NotImplementedError()
+- except:
+- f = Failure()
++ # Check privileges - must have at least DAV:read
++ try:
++ yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces)
++ except AccessDeniedError:
++ responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
++ continue
++
++ # Now retrieve all the requested properties on the HRef resource
++ yield prop_common.responseForHref(
++ request,
++ responses,
++ href,
++ child,
++ prop_common.propertyListForResource,
++ props_to_return,
++ )
++
++ prop.children = responses
++ properties_by_status[responsecode.OK].append(prop)
++ except:
++ f = Failure()
+
+- log.err("Error reading property %r for resource %s: %s"
+- % (property, self, f.value))
++ log.err("Error reading property %r for resource %s: %s" % (qname, request.uri, f.value))
+
+- status = statusForFailure(f, "getting property: %s" % (property,))
+- if status not in properties_by_status:
+- properties_by_status[status] = []
++ status = statusForFailure(f, "getting property: %s" % (qname,))
++ if status not in properties_by_status: properties_by_status[status] = []
++ properties_by_status[status].append(propertyName(qname))
+
+- raise NotImplementedError()
++ # Build the overall response
++ propstats = [
++ davxml.PropertyStatus(
++ davxml.PropertyContainer(*properties_by_status[status]),
++ davxml.Status.fromResponseCode(status)
++ )
++ for status in properties_by_status if properties_by_status[status]
++ ]
+
+- #properties_by_status[status].append(
+- # ____propertyName(property)
+- #)
+- else:
+- properties_by_status[responsecode.NOT_FOUND].append(property)
+-
+- raise NotImplementedError()
+-
+-report_DAV__expand_property = deferredGenerator(report_DAV__expand_property)
++ returnValue(MultiStatusResponse((davxml.PropertyStatusResponse(davxml.HRef(request.uri), *propstats),)))
Modified: CalendarServer/branches/users/wsanchez/deployment/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/lib-patches/Twisted/twisted.web2.dav.resource.patch 2008-11-27 04:19:41 UTC (rev 3415)
+++ CalendarServer/branches/users/wsanchez/deployment/lib-patches/Twisted/twisted.web2.dav.resource.patch 2008-11-28 19:45:46 UTC (rev 3416)
@@ -192,7 +192,22 @@
##
# DAV
##
-@@ -570,19 +646,21 @@
+@@ -553,12 +629,13 @@
+ def supportedReports(self):
+ """
+ See L{IDAVResource.supportedReports}.
+- This implementation lists the three main ACL reports.
++ This implementation lists the three main ACL reports and expand-property.
+ """
+ result = []
+ result.append(davxml.Report(davxml.ACLPrincipalPropSet(),))
+ result.append(davxml.Report(davxml.PrincipalMatch(),))
+ result.append(davxml.Report(davxml.PrincipalPropertySearch(),))
++ result.append(davxml.Report(davxml.ExpandProperty(),))
+ return result
+
+ ##
+@@ -570,19 +647,21 @@
See L{IDAVResource.authorize}.
"""
def onError(failure):
@@ -219,7 +234,7 @@
response = UnauthorizedResponse(request.credentialFactories,
request.remoteAddr)
else:
-@@ -593,7 +671,7 @@
+@@ -593,7 +672,7 @@
# class is supposed to be a FORBIDDEN status code and
# "Authorization will not help" according to RFC2616
#
@@ -228,7 +243,7 @@
d = self.checkPrivileges(request, privileges, recurse)
d.addErrback(onErrors)
-@@ -606,16 +684,21 @@
+@@ -606,16 +685,21 @@
def authenticate(self, request):
def loginSuccess(result):
@@ -254,7 +269,7 @@
authHeader = request.headers.getHeader('authorization')
-@@ -631,9 +714,10 @@
+@@ -631,9 +715,10 @@
# Try to match principals in each principal collection on the resource
def gotDetails(details):
@@ -268,7 +283,7 @@
def login(pcreds):
d = request.portal.login(pcreds, None, *request.loginInterfaces)
-@@ -641,13 +725,15 @@
+@@ -641,13 +726,15 @@
return d
@@ -288,7 +303,7 @@
##
# ACL
-@@ -656,49 +742,23 @@
+@@ -656,49 +743,23 @@
def currentPrincipal(self, request):
"""
@param request: the request being processed.
@@ -347,7 +362,7 @@
"""
@return: the L{davxml.ACL} element containing the default access control
list for this resource.
-@@ -710,6 +770,17 @@
+@@ -710,6 +771,17 @@
#
return readonlyACL
@@ -365,7 +380,7 @@
def setAccessControlList(self, acl):
"""
See L{IDAVResource.setAccessControlList}.
-@@ -748,13 +819,16 @@
+@@ -748,13 +820,16 @@
# 10. Verify that new acl is not in conflict with itself
# 11. Update acl on the resource
@@ -383,7 +398,7 @@
# Need to get list of supported privileges
supported = []
-@@ -1038,9 +1112,9 @@
+@@ -1038,9 +1113,9 @@
if myURL == "/":
# If we get to the root without any ACLs, then use the default.
@@ -395,7 +410,7 @@
# Dynamically update privileges for those ace's that are inherited.
if inheritance:
-@@ -1076,7 +1150,7 @@
+@@ -1076,7 +1151,7 @@
# Adjust ACE for inherit on this resource
children = list(ace.children)
children.remove(TwistedACLInheritable())
@@ -404,7 +419,7 @@
aces.append(davxml.ACE(*children))
else:
aces.extend(inherited_aces)
-@@ -1105,8 +1179,7 @@
+@@ -1105,8 +1180,7 @@
the child resource loop and supply those to the checkPrivileges on each child.
@param request: the L{IRequest} for the request in progress.
@@ -414,7 +429,7 @@
"""
# Get the parent ACLs with inheritance and preserve the <inheritable> element.
-@@ -1128,21 +1201,9 @@
+@@ -1128,21 +1202,9 @@
# Adjust ACE for inherit on this resource
children = list(ace.children)
children.remove(TwistedACLInheritable())
@@ -438,7 +453,7 @@
inheritedACEsforChildren = deferredGenerator(inheritedACEsforChildren)
-@@ -1152,49 +1213,69 @@
+@@ -1152,49 +1214,69 @@
This implementation returns an empty set.
"""
@@ -536,7 +551,7 @@
def samePrincipal(self, principal1, principal2):
"""
Check whether the two prinicpals are exactly the same in terms of
-@@ -1219,7 +1300,6 @@
+@@ -1219,7 +1301,6 @@
return False
def matchPrincipal(self, principal1, principal2, request):
@@ -544,7 +559,7 @@
"""
Check whether the principal1 is a principal in the set defined by
principal2.
-@@ -1244,6 +1324,9 @@
+@@ -1244,6 +1325,9 @@
if isinstance(principal1, davxml.Unauthenticated):
yield False
return
@@ -554,7 +569,7 @@
else:
yield True
return
-@@ -1271,7 +1354,6 @@
+@@ -1271,7 +1355,6 @@
assert principal2 is not None, "principal2 is None"
@@ -562,7 +577,7 @@
# Compare two HRefs and do group membership test as well
if principal1 == principal2:
yield True
-@@ -1289,6 +1371,7 @@
+@@ -1289,6 +1372,7 @@
matchPrincipal = deferredGenerator(matchPrincipal)
@@ -570,7 +585,7 @@
def principalIsGroupMember(self, principal1, principal2, request):
"""
Check whether one principal is a group member of another.
-@@ -1299,18 +1382,21 @@
+@@ -1299,18 +1383,21 @@
@return: L{Deferred} with result C{True} if principal1 is a member of principal2, C{False} otherwise
"""
@@ -603,7 +618,7 @@
def validPrincipal(self, ace_principal, request):
"""
-@@ -1351,11 +1437,16 @@
+@@ -1351,11 +1438,16 @@
@return C{True} if C{href_principal} is valid, C{False} otherwise.
This implementation tests for a href element that corresponds to
@@ -623,7 +638,7 @@
return d
def resolvePrincipal(self, principal, request):
-@@ -1432,7 +1523,7 @@
+@@ -1432,7 +1524,7 @@
log.err("DAV:self ACE is set on non-principal resource %r" % (self,))
yield None
return
@@ -632,7 +647,7 @@
if isinstance(principal, davxml.HRef):
yield principal
-@@ -1517,6 +1608,270 @@
+@@ -1517,6 +1609,270 @@
return None
##
@@ -903,7 +918,7 @@
# HTTP
##
-@@ -1525,15 +1880,11 @@
+@@ -1525,15 +1881,11 @@
#litmus = request.headers.getRawHeaders("x-litmus")
#if litmus: log.msg("*** Litmus test: %s ***" % (litmus,))
@@ -921,7 +936,7 @@
def setHeaders(response):
response = IResponse(response)
-@@ -1567,7 +1918,7 @@
+@@ -1567,7 +1919,7 @@
def findChildren(self, depth, request, callback, privileges=None, inherited_aces=None):
return succeed(None)
@@ -930,7 +945,7 @@
"""
Resource representing a WebDAV principal. (RFC 3744, section 2)
"""
-@@ -1577,7 +1928,7 @@
+@@ -1577,7 +1929,7 @@
# WebDAV
##
@@ -939,7 +954,7 @@
(dav_namespace, "alternate-URI-set"),
(dav_namespace, "principal-URL" ),
(dav_namespace, "group-member-set" ),
-@@ -1585,14 +1936,11 @@
+@@ -1585,14 +1937,11 @@
)
def davComplianceClasses(self):
@@ -955,7 +970,7 @@
def readProperty(self, property, request):
def defer():
if type(property) is tuple:
-@@ -1610,10 +1958,20 @@
+@@ -1610,10 +1959,20 @@
return davxml.PrincipalURL(davxml.HRef(self.principalURL()))
if name == "group-member-set":
@@ -978,7 +993,7 @@
if name == "resourcetype":
if self.isCollection():
-@@ -1655,7 +2013,7 @@
+@@ -1655,7 +2014,7 @@
principals. Subclasses should override this method to provide member
URLs for this resource if appropriate.
"""
@@ -987,7 +1002,7 @@
def groupMemberships(self):
"""
-@@ -1666,6 +2024,7 @@
+@@ -1666,6 +2025,7 @@
"""
unimplemented(self)
@@ -995,7 +1010,7 @@
def principalMatch(self, href):
"""
Check whether the supplied principal matches this principal or is a
-@@ -1675,10 +2034,33 @@
+@@ -1675,10 +2035,33 @@
"""
uri = str(href)
if self.principalURL() == uri:
@@ -1031,7 +1046,7 @@
class AccessDeniedError(Exception):
def __init__(self, errors):
"""
-@@ -1718,6 +2100,37 @@
+@@ -1718,6 +2101,37 @@
davxml.registerElement(TwistedACLInheritable)
davxml.ACE.allowed_children[(twisted_dav_namespace, "inheritable")] = (0, 1)
Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/customxml.py 2008-11-27 04:19:41 UTC (rev 3415)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/customxml.py 2008-11-28 19:45:46 UTC (rev 3416)
@@ -23,7 +23,8 @@
change.
"""
-from twisted.web2.dav.resource import twisted_dav_namespace
+from twisted.web2.dav.davxml import dav_namespace
+from twisted.web2.dav.davxml import twisted_dav_namespace
from twisted.web2.dav import davxml
from twistedcaldav.ical import Component as iComponent
@@ -87,6 +88,30 @@
namespace = calendarserver_namespace
name = "calendar-proxy-write"
+class CalendarProxyReadFor (davxml.WebDAVElement):
+ """
+ List of principals granting read-only proxy status.
+ (Apple Extension to CalDAV)
+ """
+ namespace = calendarserver_namespace
+ name = "calendar-proxy-read-for"
+ hidden = True
+ protected = True
+
+ allowed_children = { (dav_namespace, "href"): (0, None) }
+
+class CalendarProxyWriteFor (davxml.WebDAVElement):
+ """
+ List of principals granting read-write proxy status.
+ (Apple Extension to CalDAV)
+ """
+ namespace = calendarserver_namespace
+ name = "calendar-proxy-write-for"
+ hidden = True
+ protected = True
+
+ allowed_children = { (dav_namespace, "href"): (0, None) }
+
class TwistedCalendarPrincipalURI(davxml.WebDAVTextElement):
"""
Contains the calendarPrincipalURI value for a directory record corresponding to a principal.
Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/aggregate.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/aggregate.py 2008-11-27 04:19:41 UTC (rev 3415)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/aggregate.py 2008-11-28 19:45:46 UTC (rev 3416)
@@ -88,7 +88,11 @@
return set(self._recordTypes)
def listRecords(self, recordType):
- return self._query("listRecords", recordType)
+ records = self._query("listRecords", recordType)
+ if records is None:
+ return ()
+ else:
+ return records
def recordWithShortName(self, recordType, shortName):
return self._query("recordWithShortName", recordType, shortName)
Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/appleopendirectory.py 2008-11-27 04:19:41 UTC (rev 3415)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/appleopendirectory.py 2008-11-28 19:45:46 UTC (rev 3416)
@@ -23,10 +23,10 @@
"OpenDirectoryInitError",
]
-import itertools
import sys
import os
from random import random
+from uuid import UUID
import opendirectory
import dsattributes
@@ -474,6 +474,105 @@
return record
+ def groupsForGUID(self, guid):
+
+ # Lookup in index
+ try:
+ return self._storage(DirectoryService.recordType_groups)["groupsForGUID"][guid]
+ except KeyError:
+ return ()
+
+ def proxiesForGUID(self, recordType, guid):
+
+ # Lookup in index
+ try:
+ return self._storage(recordType)["proxiesForGUID"][guid]
+ except KeyError:
+ return ()
+
+ def readOnlyProxiesForGUID(self, recordType, guid):
+
+ # Lookup in index
+ try:
+ return self._storage(recordType)["readOnlyProxiesForGUID"][guid]
+ except KeyError:
+ return ()
+
+ def _indexGroup(self, group, guids, index):
+ for guid in guids:
+ index.setdefault(guid, set()).add(group)
+
+ _ODFields = {
+ 'fullName' : dsattributes.kDS1AttrDistinguishedName,
+ 'firstName' : dsattributes.kDS1AttrFirstName,
+ 'lastName' : dsattributes.kDS1AttrLastName,
+ 'emailAddresses' : dsattributes.kDSNAttrEMailAddress,
+ }
+
+ _toODRecordTypes = {
+ DirectoryService.recordType_users :
+ dsattributes.kDSStdRecordTypeUsers,
+ DirectoryService.recordType_locations :
+ dsattributes.kDSStdRecordTypePlaces,
+ DirectoryService.recordType_groups :
+ dsattributes.kDSStdRecordTypeGroups,
+ DirectoryService.recordType_resources :
+ dsattributes.kDSStdRecordTypeResources,
+ }
+
+ _fromODRecordTypes = dict([(b, a) for a, b in _toODRecordTypes.iteritems()])
+
+ def recordsMatchingFields(self, fields, operand="or", recordType=None):
+
+ # Note that OD applies case-sensitivity globally across the entire
+ # query, not per expression, so the current code uses whatever is
+ # specified in the last field in the fields list
+
+ operand = (dsquery.expression.OR if operand == "or"
+ else dsquery.expression.AND)
+
+ expressions = []
+ for field, value, caseless, matchType in fields:
+ if field in self._ODFields:
+ ODField = self._ODFields[field]
+ if matchType == "starts-with":
+ comparison = dsattributes.eDSStartsWith
+ else:
+ comparison = dsattributes.eDSContains
+ expressions.append(dsquery.match(ODField, value, comparison))
+
+
+ if recordType is None:
+ recordTypes = self._toODRecordTypes.values()
+ else:
+ recordTypes = (self._toODRecordTypes[recordType],)
+
+ for recordType in recordTypes:
+
+ try:
+ self.log_info("Calling OD: Type %s, Operand %s, Caseless %s, %s" % (recordType, operand, caseless, fields))
+ results = opendirectory.queryRecordsWithAttributes(
+ self.directory,
+ dsquery.expression(operand, expressions).generate(),
+ caseless,
+ recordType,
+ [ dsattributes.kDS1AttrGeneratedUID ]
+ )
+ self.log_info("Got back %d records from OD" % (len(results),))
+ for key, val in results.iteritems():
+ self.log_debug("OD result: %s %s" % (key, val))
+ try:
+ guid = val[dsattributes.kDS1AttrGeneratedUID]
+ rec = self.recordWithGUID(guid)
+ if rec:
+ yield rec
+ except KeyError:
+ pass
+
+ except Exception, e:
+ self.log_error("OD search failed: %s" % (e,))
+ raise
+
def reloadCache(self, recordType, shortName=None, guid=None):
if shortName:
self.log_info("Faulting record %s into %s record cache" % (shortName, recordType))
@@ -488,6 +587,12 @@
disabledNames = set()
disabledGUIDs = set()
+
+ if recordType == DirectoryService.recordType_groups:
+ groupsForGUID = {}
+ elif recordType in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
+ proxiesForGUID = {}
+ readOnlyProxiesForGUID = {}
else:
storage = self._records[recordType]
@@ -496,6 +601,12 @@
disabledNames = storage["disabled names"]
disabledGUIDs = storage["disabled guids"]
+
+ if recordType == DirectoryService.recordType_groups:
+ groupsForGUID = storage["groupsForGUID"]
+ elif recordType in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
+ proxiesForGUID = storage["proxiesForGUID"]
+ readOnlyProxiesForGUID = storage["readOnlyProxiesForGUID"]
for (recordShortName, value) in results:
enabledForCalendaring = True
@@ -638,6 +749,15 @@
records[record.shortName] = guids[record.guid] = record
self.log_debug("Added record %s to OD record cache" % (record,))
+ # Do group indexing if needed
+ if recordType == DirectoryService.recordType_groups:
+ self._indexGroup(record, record._memberGUIDs, groupsForGUID)
+
+ # Do proxy indexing if needed
+ elif recordType in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
+ self._indexGroup(record, record._proxyGUIDs, proxiesForGUID)
+ self._indexGroup(record, record._readOnlyProxyGUIDs, readOnlyProxiesForGUID)
+
if shortName is None and guid is None:
#
# Replace the entire cache
@@ -650,6 +770,15 @@
"disabled guids": disabledGUIDs,
}
+ # Add group indexing if needed
+ if recordType == DirectoryService.recordType_groups:
+ storage["groupsForGUID"] = groupsForGUID
+
+ # Add proxy indexing if needed
+ elif recordType in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
+ storage["proxiesForGUID"] = proxiesForGUID
+ storage["readOnlyProxiesForGUID"] = readOnlyProxiesForGUID
+
def rot():
storage["status"] = "stale"
removals = set()
@@ -885,9 +1014,7 @@
yield userRecord
def groups(self):
- for groupRecord in self.service.recordsForType(DirectoryService.recordType_groups).itervalues():
- if self.guid in groupRecord._memberGUIDs:
- yield groupRecord
+ return self.service.groupsForGUID(self.guid)
def proxies(self):
if self.recordType not in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
@@ -901,12 +1028,10 @@
yield proxyRecord
def proxyFor(self):
- for proxyRecord in itertools.chain(
- self.service.recordsForType(DirectoryService.recordType_resources).itervalues(),
- self.service.recordsForType(DirectoryService.recordType_locations).itervalues(),
- ):
- if self.guid in proxyRecord._proxyGUIDs:
- yield proxyRecord
+ result = set()
+ result.update(self.service.proxiesForGUID(DirectoryService.recordType_resources, self.guid))
+ result.update(self.service.proxiesForGUID(DirectoryService.recordType_locations, self.guid))
+ return result
def readOnlyProxies(self):
if self.recordType not in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
@@ -920,12 +1045,10 @@
yield proxyRecord
def readOnlyProxyFor(self):
- for proxyRecord in itertools.chain(
- self.service.recordsForType(DirectoryService.recordType_resources).itervalues(),
- self.service.recordsForType(DirectoryService.recordType_locations).itervalues(),
- ):
- if self.guid in proxyRecord._readOnlyProxyGUIDs:
- yield proxyRecord
+ result = set()
+ result.update(self.service.readOnlyProxiesForGUID(DirectoryService.recordType_resources, self.guid))
+ result.update(self.service.readOnlyProxiesForGUID(DirectoryService.recordType_locations, self.guid))
+ return result
def verifyCredentials(self, credentials):
if isinstance(credentials, UsernamePassword):
Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/calendaruserproxy.py 2008-11-27 04:19:41 UTC (rev 3415)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/calendaruserproxy.py 2008-11-28 19:45:46 UTC (rev 3416)
@@ -132,6 +132,15 @@
else:
return super(CalendarUserProxyPrincipalResource, self).resourceType()
+ def isProxyType(self, read_write):
+ if (
+ read_write and self.proxyType == "calendar-proxy-write" or
+ not read_write and self.proxyType == "calendar-proxy-read"
+ ):
+ return True
+ else:
+ return False
+
def isCollection(self):
return True
Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/principal.py 2008-11-27 04:19:41 UTC (rev 3415)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/principal.py 2008-11-28 19:45:46 UTC (rev 3416)
@@ -425,6 +425,14 @@
d = waitForDeferred(self.groupMemberships())
yield d
memberships = d.getResult()
+
+ d = waitForDeferred(self.proxyFor(True))
+ yield d
+ proxyFor = d.getResult()
+
+ d = waitForDeferred(self.proxyFor(False))
+ yield d
+ readOnlyProxyFor = d.getResult()
yield "".join((
"""<div class="directory-listing">"""
@@ -446,6 +454,8 @@
"""\nAlternate URIs:\n""" , format_list(format_link(u) for u in self.alternateURIs()),
"""\nGroup members:\n""" , format_principals(members),
"""\nGroup memberships:\n""" , format_principals(memberships),
+ """\nRead-write Proxy For:\n""" , format_principals(proxyFor),
+ """\nRead-only Proxy For:\n""" , format_principals(readOnlyProxyFor),
"""</pre></blockquote></div>""",
output
))
@@ -546,6 +556,40 @@
yield groups
+ @deferredGenerator
+ def proxyFor(self, read_write, resolve_memberships=True):
+ proxyFors = set()
+
+ if resolve_memberships:
+ memberships = self._getRelatives("groups")
+ for membership in memberships:
+ d = waitForDeferred(membership.proxyFor(read_write, False))
+ yield d
+ results = d.getResult()
+ proxyFors.update(results)
+
+ if config.EnableProxyPrincipals:
+ # Get any directory specified proxies
+ if read_write:
+ directoryProxies = self._getRelatives("proxyFor", proxy='read-write')
+ else:
+ directoryProxies = self._getRelatives("readOnlyProxyFor", proxy='read-only')
+ proxyFors.update([subprincipal.parent for subprincipal in directoryProxies])
+
+ # Get proxy group UIDs and map to principal resources
+ proxies = []
+ d = waitForDeferred(self._calendar_user_proxy_index().getMemberships(self.principalUID()))
+ yield d
+ memberships = d.getResult()
+ for uid in memberships:
+ subprincipal = self.parent.principalForUID(uid)
+ if subprincipal and subprincipal.isProxyType(read_write):
+ proxies.append(subprincipal.parent)
+
+ proxyFors.update(proxies)
+
+ yield proxyFors
+
def principalCollections(self):
return self.parent.principalCollections()
@@ -595,6 +639,14 @@
yield d
memberships = d.getResult()
+ d = waitForDeferred(self.proxyFor(True))
+ yield d
+ proxyFor = d.getResult()
+
+ d = waitForDeferred(self.proxyFor(False))
+ yield d
+ readOnlyProxyFor = d.getResult()
+
yield "".join((
"""<div class="directory-listing">"""
"""<h1>Principal Details</h1>"""
@@ -615,6 +667,8 @@
"""\nAlternate URIs:\n""" , format_list(format_link(u) for u in self.alternateURIs()),
"""\nGroup members:\n""" , format_principals(members),
"""\nGroup memberships:\n""" , format_principals(memberships),
+ """\nRead-write Proxy For:\n""" , format_principals(proxyFor),
+ """\nRead-only Proxy For:\n""" , format_principals(readOnlyProxyFor),
"""\nCalendar homes:\n""" , format_list(format_link(u) for u in self.calendarHomeURLs()),
"""\nCalendar user addresses:\n""" , format_list(format_link(a) for a in self.calendarUserAddresses()),
"""</pre></blockquote></div>""",
Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_opendirectoryrecords.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_opendirectoryrecords.py 2008-11-27 04:19:41 UTC (rev 3415)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_opendirectoryrecords.py 2008-11-28 19:45:46 UTC (rev 3416)
@@ -300,7 +300,102 @@
("EDB9EE55-31F2-4EA9-B5FB-D8AE2A8BA35E", "62368DDF-0C62-4C97-9A58-DE9FD46131A0", "D10F3EE0-5014-41D3-8488-3819D3EF3B2A"),
)
-def fakeODRecord(fullName, shortName=None, guid=None, email=None, addLocator=True):
+ def test_groupmembers(self):
+ self._service.fakerecords = {
+ DirectoryService.recordType_users: [
+ fakeODRecord("User 01"),
+ fakeODRecord("User 02"),
+ ],
+ DirectoryService.recordType_groups: [
+ fakeODRecord("Group 01", members=[
+ guidForShortName("user01"),
+ guidForShortName("user02"),
+ ]),
+ fakeODRecord("Group 02", members=[
+ guidForShortName("resource01"),
+ guidForShortName("user02"),
+ ]),
+ ],
+ DirectoryService.recordType_resources: [
+ fakeODRecord("Resource 01"),
+ fakeODRecord("Resource 02"),
+ ],
+ DirectoryService.recordType_locations: [
+ fakeODRecord("Location 01"),
+ fakeODRecord("Location 02"),
+ ],
+ }
+
+ self._service.reloadCache(DirectoryService.recordType_users)
+ self._service.reloadCache(DirectoryService.recordType_groups)
+ self._service.reloadCache(DirectoryService.recordType_resources)
+ self._service.reloadCache(DirectoryService.recordType_locations)
+
+ group1 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group01")
+ self.assertTrue(group1 is not None)
+
+ group2 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group02")
+ self.assertTrue(group2 is not None)
+
+ user1 = self._service.recordWithShortName(DirectoryService.recordType_users, "user01")
+ self.assertTrue(user1 is not None)
+ self.assertEqual(set((group1,)), user1.groups())
+
+ user2 = self._service.recordWithShortName(DirectoryService.recordType_users, "user02")
+ self.assertTrue(user2 is not None)
+ self.assertEqual(set((group1, group2)), user2.groups())
+
+ self._service.fakerecords[DirectoryService.recordType_groups] = [
+ fakeODRecord("Group 01", members=[
+ guidForShortName("user01"),
+ ]),
+ fakeODRecord("Group 02", members=[
+ guidForShortName("resource01"),
+ guidForShortName("user02"),
+ ]),
+ ]
+ self._service.reloadCache(DirectoryService.recordType_groups)
+
+ group1 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group01")
+ self.assertTrue(group1 is not None)
+
+ group2 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group02")
+ self.assertTrue(group2 is not None)
+
+ user1 = self._service.recordWithShortName(DirectoryService.recordType_users, "user01")
+ self.assertTrue(user1 is not None)
+ self.assertEqual(set((group1,)), user1.groups())
+
+ user2 = self._service.recordWithShortName(DirectoryService.recordType_users, "user02")
+ self.assertTrue(user2 is not None)
+ self.assertEqual(set((group2,)), user2.groups())
+
+ self._service.fakerecords[DirectoryService.recordType_groups] = [
+ fakeODRecord("Group 03", members=[
+ guidForShortName("user01"),
+ guidForShortName("user02"),
+ ]),
+ ]
+ self._service.reloadCache(DirectoryService.recordType_groups, guid=guidForShortName("group03"))
+
+ group1 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group01")
+ self.assertTrue(group1 is not None)
+
+ group2 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group02")
+ self.assertTrue(group2 is not None)
+
+ group3 = self._service.recordWithShortName(DirectoryService.recordType_groups, "group03")
+ self.assertTrue(group2 is not None)
+
+ user1 = self._service.recordWithShortName(DirectoryService.recordType_users, "user01")
+ self.assertTrue(user1 is not None)
+ self.assertEqual(set((group1, group3)), user1.groups())
+
+ user2 = self._service.recordWithShortName(DirectoryService.recordType_users, "user02")
+ self.assertTrue(user2 is not None)
+ self.assertEqual(set((group2, group3)), user2.groups())
+
+def fakeODRecord(fullName, shortName=None, guid=None, email=None, addLocator=True, members=None):
if shortName is None:
shortName = shortNameForFullName(fullName)
@@ -318,6 +413,9 @@
dsattributes.kDSNAttrEMailAddress: email,
dsattributes.kDSNAttrMetaNodeLocation: "/LDAPv3/127.0.0.1",
}
+
+ if members:
+ attrs[dsattributes.kDSNAttrGroupMembers] = members
if addLocator:
attrs[dsattributes.kDSNAttrServicesLocator] = "FE588D50-0514-4DF9-BCB5-8ECA5F3DA274:030572AE-ABEC-4E0F-83C9-FCA304769E5F:calendar"
Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_proxyprincipalmembers.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_proxyprincipalmembers.py 2008-11-27 04:19:41 UTC (rev 3415)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/directory/test/test_proxyprincipalmembers.py 2008-11-28 19:45:46 UTC (rev 3416)
@@ -14,7 +14,7 @@
# limitations under the License.
##
-from twisted.internet.defer import DeferredList
+from twisted.internet.defer import DeferredList, inlineCallbacks
from twisted.web2.dav import davxml
from twistedcaldav.directory.directory import DirectoryService
@@ -74,6 +74,13 @@
d = principal.groupMemberships()
d.addCallback(gotMemberships)
return d
+
+ @inlineCallbacks
+ def _proxyForTest(self, recordType, recordName, expectedProxies, read_write):
+ principal = self._getPrincipalByShortName(recordType, recordName)
+ proxies = (yield principal.proxyFor(read_write))
+ proxies = set([principal.displayName() for principal in proxies])
+ self.assertEquals(proxies, set(expectedProxies))
def test_groupMembersRegular(self):
"""
@@ -254,3 +261,20 @@
self.assertEquals(notifier.changedCount, 1)
finally:
DirectoryPrincipalResource.cacheNotifierFactory = oldCacheNotifier
+
+ def test_proxyFor(self):
+
+ return self._proxyForTest(
+ DirectoryService.recordType_users, "wsanchez",
+ ("Mecury Seven", "Gemini Twelve", "Apollo Eleven", "Orion", ),
+ True
+ )
+
+ def test_readOnlyProxyFor(self):
+
+ return self._proxyForTest(
+ DirectoryService.recordType_users, "wsanchez",
+ ("Non-calendar proxy", ),
+ False
+ )
+
Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/extensions.py 2008-11-27 04:19:41 UTC (rev 3415)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/extensions.py 2008-11-28 19:45:46 UTC (rev 3416)
@@ -636,7 +636,7 @@
return f
- for qname in qnames:
+ for qname in sorted(qnames):
d = self.readProperty(qname, request)
d.addCallback(gotProperty)
d.addErrback(gotError, qname)
Modified: CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/resource.py 2008-11-27 04:19:41 UTC (rev 3415)
+++ CalendarServer/branches/users/wsanchez/deployment/twistedcaldav/resource.py 2008-11-28 19:45:46 UTC (rev 3416)
@@ -603,6 +603,8 @@
(caldav_namespace, "calendar-user-address-set"),
(caldav_namespace, "schedule-inbox-URL" ),
(caldav_namespace, "schedule-outbox-URL" ),
+ (calendarserver_namespace, "calendar-proxy-read-for" ),
+ (calendarserver_namespace, "calendar-proxy-write-for" ),
)
@classmethod
@@ -616,52 +618,78 @@
def isCollection(self):
return True
+ @deferredGenerator
def readProperty(self, property, request):
- def defer():
- if type(property) is tuple:
- qname = property
- else:
- qname = property.qname()
+ if type(property) is tuple:
+ qname = property
+ else:
+ qname = property.qname()
- namespace, name = qname
+ namespace, name = qname
- if namespace == caldav_namespace:
- if name == "calendar-home-set":
- return caldavxml.CalendarHomeSet(
- *[davxml.HRef(url) for url in self.calendarHomeURLs()]
- )
+ if namespace == caldav_namespace:
+ if name == "calendar-home-set":
+ yield caldavxml.CalendarHomeSet(
+ *[davxml.HRef(url) for url in self.calendarHomeURLs()]
+ )
+ return
- if name == "calendar-user-address-set":
- return succeed(caldavxml.CalendarUserAddressSet(
- *[davxml.HRef(uri) for uri in self.calendarUserAddresses()]
- ))
+ elif name == "calendar-user-address-set":
+ yield caldavxml.CalendarUserAddressSet(
+ *[davxml.HRef(uri) for uri in self.calendarUserAddresses()]
+ )
+ return
- if name == "schedule-inbox-URL":
- url = self.scheduleInboxURL()
- if url is None:
- return None
- else:
- return caldavxml.ScheduleInboxURL(davxml.HRef(url))
+ elif name == "schedule-inbox-URL":
+ url = self.scheduleInboxURL()
+ if url is None:
+ yield None
+ return
+ else:
+ yield caldavxml.ScheduleInboxURL(davxml.HRef(url))
+ return
- if name == "schedule-outbox-URL":
- url = self.scheduleOutboxURL()
- if url is None:
- return None
- else:
- return caldavxml.ScheduleOutboxURL(davxml.HRef(url))
+ elif name == "schedule-outbox-URL":
+ url = self.scheduleOutboxURL()
+ if url is None:
+ yield None
+ return
+ else:
+ yield caldavxml.ScheduleOutboxURL(davxml.HRef(url))
+ return
- elif namespace == calendarserver_namespace:
- if name == "dropbox-home-URL" and config.EnableDropBox:
- url = self.dropboxURL()
- if url is None:
- return None
- else:
- return customxml.DropBoxHomeURL(davxml.HRef(url))
+ elif namespace == calendarserver_namespace:
+ if name == "dropbox-home-URL" and config.EnableDropBox:
+ url = self.dropboxURL()
+ if url is None:
+ yield None
+ return
+ else:
+ yield customxml.DropBoxHomeURL(davxml.HRef(url))
+ return
- return super(CalendarPrincipalResource, self).readProperty(property, request)
+ elif name == "calendar-proxy-read-for":
+ d = waitForDeferred(self.proxyFor(False))
+ yield d
+ results = d.getResult()
+ yield customxml.CalendarProxyReadFor(
+ *[davxml.HRef(principal.principalURL()) for principal in results]
+ )
+ return
- return maybeDeferred(defer)
+ elif name == "calendar-proxy-write-for":
+ d = waitForDeferred(self.proxyFor(True))
+ yield d
+ results = d.getResult()
+ yield customxml.CalendarProxyWriteFor(
+ *[davxml.HRef(principal.principalURL()) for principal in results]
+ )
+ return
+ d = waitForDeferred(super(CalendarPrincipalResource, self).readProperty(property, request))
+ yield d
+ yield d.getResult()
+
def groupMembers(self):
return succeed(())
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20081128/c19b5c6e/attachment-0001.html>
More information about the calendarserver-changes
mailing list