[CalendarServer-changes] [3019] CalendarServer/branches/users/sagen/principal-property-search-3017
source_changes at macosforge.org
source_changes at macosforge.org
Thu Sep 18 15:52:37 PDT 2008
Revision: 3019
http://trac.macosforge.org/projects/calendarserver/changeset/3019
Author: sagen at apple.com
Date: 2008-09-18 15:52:36 -0700 (Thu, 18 Sep 2008)
Log Message:
-----------
Merging forward
Modified Paths:
--------------
CalendarServer/branches/users/sagen/principal-property-search-3017/conf/accounts-test.xml
CalendarServer/branches/users/sagen/principal-property-search-3017/lib-patches/Twisted/twisted.web2.dav.element.rfc3744.patch
CalendarServer/branches/users/sagen/principal-property-search-3017/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/customxml.py
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/aggregate.py
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/apache.py
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/appleopendirectory.py
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/directory.py
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/idirectory.py
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/principal.py
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/sqldb.py
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/sudo.py
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/test/test_opendirectory.py
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/xmlaccountsparser.py
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/xmlfile.py
CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/resource.py
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/conf/accounts-test.xml
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/conf/accounts-test.xml 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/conf/accounts-test.xml 2008-09-18 22:52:36 UTC (rev 3019)
@@ -24,12 +24,16 @@
<guid>admin</guid>
<password>admin</password>
<name>Super User</name>
+ <first-name>Super</first-name>
+ <last-name>User</last-name>
</user>
<user>
<uid>apprentice</uid>
<guid>apprentice</guid>
<password>apprentice</password>
<name>Apprentice Super User</name>
+ <first-name>Apprentice</first-name>
+ <last-name>Super User</last-name>
</user>
<user repeat="99">
<uid>user%02d</uid>
@@ -37,6 +41,8 @@
<password>user%02d</password>
<name>User %02d</name>
<cuaddr>mailto:user%02d at example.com</cuaddr>
+ <first-name>User</first-name>
+ <last-name>%02d</last-name>
</user>
<user repeat="10">
<uid>public%02d</uid>
@@ -44,6 +50,8 @@
<password>public%02d</password>
<name>Public %02d</name>
<cuaddr>mailto:public%02d at example.com</cuaddr>
+ <first-name>Public</first-name>
+ <last-name>%02d</last-name>
</user>
<location repeat="10">
<uid>location%02d</uid>
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/lib-patches/Twisted/twisted.web2.dav.element.rfc3744.patch
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/lib-patches/Twisted/twisted.web2.dav.element.rfc3744.patch 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/lib-patches/Twisted/twisted.web2.dav.element.rfc3744.patch 2008-09-18 22:52:36 UTC (rev 3019)
@@ -81,3 +81,11 @@
# This element can be empty when uses in supported-report-set
if not len(self.children):
+@@ -705,6 +714,7 @@
+ (dav_namespace, "prop" ): (0, 1),
+ (dav_namespace, "apply-to-principal-collection-set"): (0, 1),
+ }
++ allowed_attributes = { "test": False }
+
+ class PropertySearch (WebDAVElement):
+ """
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch 2008-09-18 22:52:36 UTC (rev 3019)
@@ -2,19 +2,263 @@
===================================================================
--- twisted/web2/dav/method/report_principal_property_search.py (revision 19773)
+++ twisted/web2/dav/method/report_principal_property_search.py (working copy)
-@@ -127,13 +127,8 @@
+@@ -51,12 +51,18 @@
+ raise ValueError("%s expected as root element, not %s."
+ % (davxml.PrincipalPropertySearch.sname(), principal_property_search.sname()))
+
++ # Should we AND (the default) or OR (if test="anyof")?
++ testMode = principal_property_search.attributes.get("test", "allof")
++ if testMode not in ("allof", "anyof"):
++ raise ValueError("Unknown value for test attribute: %s" % (testMode,))
++ operand = "and" if testMode == "allof" else "or"
++
+ # Only handle Depth: 0
+ depth = request.headers.getHeader("depth", "0")
+ if depth != "0":
+ log.err("Error in prinicpal-property-search REPORT, Depth set to %s" % (depth,))
+ raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
+-
++
+ # Get a single DAV:prop element from the REPORT request body
+ propertiesForResource = None
+ propElement = None
+@@ -93,73 +99,155 @@
+ else:
+ return False
+
+- def propertySearch(resource, request):
++ def propertySearch(resource, request, operand):
+ """
+ Test the resource to see if it contains properties matching the
+ property-search specification in this report.
+ @param resource: the L{DAVFile} for the resource to test.
+ @param request: the current request.
++ @param operand: "and" or "or"
+ @return: True if the resource has matching properties, False otherwise.
+ """
+- for props, match in propertySearches:
+- # Test each property
+- for prop in props:
+- try:
+- propvalue = waitForDeferred(resource.readProperty(prop.qname(), request))
+- yield propvalue
+- propvalue = propvalue.getResult()
+- if propvalue and not nodeMatch(propvalue, match):
++
++ if operand == "and":
++ for props, match in propertySearches:
++ # Test each property
++ for prop in props:
++ try:
++ propvalue = waitForDeferred(resource.readProperty(prop.qname(), request))
++ yield propvalue
++ propvalue = propvalue.getResult()
++ if propvalue:
++ if not nodeMatch(propvalue, match):
++ yield False
++ return
++ except HTTPError:
++ # No property => no match
+ yield False
+ return
+- except HTTPError:
+- # No property => no match
+- yield False
+- return
+-
+- yield True
++ # we hit on every property
++ yield True
+
++ else: # "or"
++ for props, match in propertySearches:
++ # Test each property
++ for prop in props:
++ try:
++ propvalue = waitForDeferred(resource.readProperty(prop.qname(), request))
++ yield propvalue
++ propvalue = propvalue.getResult()
++ if propvalue:
++ if nodeMatch(propvalue, match):
++ yield True
++ return
++ except HTTPError:
++ # No property
++ pass
++ # we didn't hit any
++ yield False
++
+ propertySearch = deferredGenerator(propertySearch)
+
++
+ # Run report
+ try:
+- resources = []
+- responses = []
++
++ # See if we can take advantage of the directory
++ fields = []
++ nonDirectorySearches = []
++ for props, match in propertySearches:
++ nonDirectoryProps = []
++ for prop in props:
++ try:
++ fieldName = self.propertyToField(prop)
++ except AttributeError:
++ fieldName = None
++ if fieldName:
++ fields.append((fieldName, match))
++ else:
++ nonDirectoryProps.append(prop)
++ if nonDirectoryProps:
++ nonDirectorySearches.append((nonDirectoryProps, match))
++
++ matchingResources = []
matchcount = 0
- if applyTo:
+- if applyTo:
- # Get the principal collection set
- pset = waitForDeferred(self.principalCollections(request))
- yield pset
- pset = pset.getResult()
--
++ if (
++ (operand == "or" and nonDirectorySearches) or
++ (operand == "and" and nonDirectorySearches and not fields)
++ ):
++ # These are the situations in which we need to iterate all the
++ # resources
++ resources = []
+
- for phref in pset:
- uri = str(phref)
-+ for principalCollection in self.principalCollections():
-+ uri = principalCollection.principalCollectionURL()
- resource = waitForDeferred(request.locateResource(uri))
- yield resource
- resource = resource.getResult()
+- resource = waitForDeferred(request.locateResource(uri))
+- yield resource
+- resource = resource.getResult()
+- if resource:
+- resources.append((resource, uri))
+- else:
+- resources.append((self, request.uri))
++ if applyTo:
++ for principalCollection in self.principalCollections():
++ uri = principalCollection.principalCollectionURL()
++ resource = waitForDeferred(request.locateResource(uri))
++ yield resource
++ resource = resource.getResult()
++ if resource:
++ resources.append((resource, uri))
++ else:
++ resources.append((self, request.uri))
+
+- # Loop over all collections and principal resources within
+- for resource, ruri in resources:
++ # Loop over all collections and principal resources within
++ for resource, ruri in resources:
+
+- # 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(resource.inheritedACEsforChildren(request))
+- yield filteredaces
+- filteredaces = filteredaces.getResult()
++ # 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(resource.inheritedACEsforChildren(request))
++ yield filteredaces
++ filteredaces = filteredaces.getResult()
+
+- children = []
+- d = waitForDeferred(resource.findChildren("infinity", request, lambda x, y: children.append((x,y)),
+- privileges=(davxml.Read(),), inherited_aces=filteredaces))
+- yield d
+- d.getResult()
++ children = []
++ d = waitForDeferred(resource.findChildren("infinity", request,
++ lambda x, y: children.append((x,y)),
++ privileges=(davxml.Read(),), inherited_aces=filteredaces))
++ yield d
++ d.getResult()
+
+- for child, uri in children:
+- if isPrincipalResource(child):
+- d = waitForDeferred(propertySearch(child, request))
++ for child, uri in children:
++ if isPrincipalResource(child):
++ d = waitForDeferred(propertySearch(child, request,
++ operand))
++ yield d
++ d = d.getResult()
++ if d:
++ # Check size of results is within limit
++ matchcount += 1
++ if matchcount > max_number_of_matches:
++ raise NumberOfMatchesWithinLimits
++
++ matchingResources.append(child)
++
++
++ elif fields: # search the directory
++ try:
++ recordType = self.recordType
++ except AttributeError:
++ recordType = None
++
++ for record in self.directory.recordsMatchingFields(fields,
++ operand=operand, recordType=recordType):
++
++ resource = self.principalForRecord(record)
++ url = resource.url()
++
++ if not nonDirectorySearches:
++ # We've determined this is a matching resource
++
++ matchcount += 1
++ if matchcount > max_number_of_matches:
++ raise NumberOfMatchesWithinLimits
++ matchingResources.append(resource)
++
++ elif operand == "and":
++ # Further narrowing down needs to take place by examining
++ # the resource's DAV properties
++
++ d = waitForDeferred(propertySearch(resource, request,
++ operand))
+ yield d
+ d = d.getResult()
+ if d:
+@@ -167,18 +255,26 @@
+ matchcount += 1
+ if matchcount > max_number_of_matches:
+ raise NumberOfMatchesWithinLimits
+-
+- d = waitForDeferred(prop_common.responseForHref(
+- request,
+- responses,
+- davxml.HRef.fromString(uri),
+- child,
+- propertiesForResource,
+- propElement
+- ))
+- yield d
+- d.getResult()
++ matchingResources.append(resource)
+
++
++
++ # Generate the response
++ responses = []
++ for resource in matchingResources:
++ url = resource.url()
++ d = waitForDeferred(prop_common.responseForHref(
++ request,
++ responses,
++ davxml.HRef.fromString(url),
++ resource,
++ propertiesForResource,
++ propElement
++ ))
++ yield d
++ d.getResult()
++
++
+ except NumberOfMatchesWithinLimits:
+ log.err("Too many matching components in prinicpal-property-search report")
+ raise HTTPError(ErrorResponse(
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/customxml.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/customxml.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -290,6 +290,30 @@
protected = True
hidden = True
+class FirstNameProperty (davxml.WebDAVTextElement):
+ """
+ A property representing first name of a principal
+ """
+ namespace = calendarserver_namespace
+ name = "first-name"
+ protected = True
+
+class LastNameProperty (davxml.WebDAVTextElement):
+ """
+ A property representing last name of a principal
+ """
+ namespace = calendarserver_namespace
+ name = "last-name"
+ protected = True
+
+class EMailProperty (davxml.WebDAVTextElement):
+ """
+ A property representing email address of a principal
+ """
+ namespace = calendarserver_namespace
+ name = "email-address"
+ protected = True
+
class IScheduleInbox (davxml.WebDAVEmptyElement):
"""
Denotes the resourcetype of a iSchedule Inbox.
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/aggregate.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/aggregate.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/aggregate.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -103,6 +103,19 @@
def recordWithCalendarUserAddress(self, address):
return self._queryAll("recordWithCalendarUserAddress", address)
+ def recordsMatchingFields(self, fields, caseInsensitive=True, operand="or",
+ recordType=None):
+ if recordType:
+ services = (self.serviceForRecordType(recordType),)
+ else:
+ services = set(self._recordTypes.values())
+
+ for service in services:
+ for record in service.recordsMatchingFields(fields,
+ caseInsensitive=caseInsensitive, operand=operand,
+ recordType=recordType):
+ yield record
+
def serviceForRecordType(self, recordType):
try:
return self._recordTypes[recordType]
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/apache.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/apache.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/apache.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -129,6 +129,9 @@
guid = None,
shortName = shortName,
fullName = None,
+ firstName = None,
+ lastName = None,
+ emailAddress = None,
calendarUserAddresses = set(),
autoSchedule = False,
)
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/appleopendirectory.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/appleopendirectory.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -460,6 +460,100 @@
return record
+
+ _ODFields = {
+ 'fullName' : dsattributes.kDS1AttrDistinguishedName,
+ 'firstName' : dsattributes.kDS1AttrFirstName,
+ 'lastName' : dsattributes.kDS1AttrLastName,
+ 'emailAddress' : 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, caseInsensitive=True, operand="or",
+ recordType=None):
+
+ comparison = dsattributes.eDSStartsWith
+ operand = (dsquery.expression.OR if operand == "or"
+ else dsquery.expression.AND)
+
+ expressions = []
+ for field, value in fields:
+ if field in self._ODFields:
+ ODField = self._ODFields[field]
+ 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: %s %s %s" % (recordType, operand,
+ fields))
+ results = opendirectory.queryRecordsWithAttributes(
+ self.directory,
+ dsquery.expression(operand, expressions).generate(),
+ caseInsensitive,
+ recordType,
+ [
+ dsattributes.kDS1AttrGeneratedUID,
+ dsattributes.kDS1AttrFirstName,
+ dsattributes.kDS1AttrLastName,
+ dsattributes.kDSNAttrEMailAddress,
+ dsattributes.kDS1AttrDistinguishedName,
+ dsattributes.kDSNAttrMetaNodeLocation,
+ ]
+ )
+ 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:
+ calendarUserAddresses = set()
+ enabledForCalendaring = False
+ if val.has_key(dsattributes.kDSNAttrEMailAddress):
+ enabledForCalendaring = True
+ calendarUserAddresses.add(val[dsattributes.kDSNAttrEMailAddress])
+ rec = OpenDirectoryRecord(
+ service = self,
+ recordType = self._fromODRecordTypes[recordType],
+ guid = val[dsattributes.kDS1AttrGeneratedUID],
+ nodeName = val[dsattributes.kDSNAttrMetaNodeLocation],
+ shortName = key,
+ fullName = val.get(dsattributes.kDS1AttrDistinguishedName, ""),
+ firstName = val.get(dsattributes.kDS1AttrFirstName, ""),
+ lastName = val.get(dsattributes.kDS1AttrLastName, ""),
+ emailAddress = val.get(dsattributes.kDSNAttrEMailAddress, ""),
+ calendarUserAddresses = calendarUserAddresses,
+ autoSchedule = False,
+ enabledForCalendaring = enabledForCalendaring,
+ memberGUIDs = (),
+ proxyGUIDs = (),
+ readOnlyProxyGUIDs = (),
+ )
+ yield rec
+ except Exception, e:
+ self.log_error("Failed to convert OD result into record: %s %s" % (val, e))
+ raise
+
+ 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))
@@ -467,7 +561,7 @@
self.log_info("Reloading %s record cache" % (recordType,))
results = self._queryDirectory(recordType, shortName=shortName, guid=guid)
-
+
if shortName is None and guid is None:
records = {}
guids = {}
@@ -533,6 +627,9 @@
# Now get useful record info.
recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
recordFullName = value.get(dsattributes.kDS1AttrDistinguishedName)
+ recordFirstName = value.get(dsattributes.kDS1AttrFirstName)
+ recordLastName = value.get(dsattributes.kDS1AttrLastName)
+ recordEmailAddress = value.get(dsattributes.kDSNAttrEMailAddress)
recordNodeName = value.get(dsattributes.kDSNAttrMetaNodeLocation)
if not recordGUID:
@@ -581,6 +678,9 @@
nodeName = recordNodeName,
shortName = recordShortName,
fullName = recordFullName,
+ firstName = recordFirstName,
+ lastName = recordLastName,
+ emailAddress = recordEmailAddress,
calendarUserAddresses = calendarUserAddresses,
autoSchedule = autoSchedule,
enabledForCalendaring = enabledForCalendaring,
@@ -665,6 +765,8 @@
attrs = [
dsattributes.kDS1AttrGeneratedUID,
dsattributes.kDS1AttrDistinguishedName,
+ dsattributes.kDS1AttrFirstName,
+ dsattributes.kDS1AttrLastName,
dsattributes.kDSNAttrEMailAddress,
dsattributes.kDSNAttrServicesLocator,
dsattributes.kDSNAttrMetaNodeLocation,
@@ -827,6 +929,7 @@
"""
def __init__(
self, service, recordType, guid, nodeName, shortName, fullName,
+ firstName, lastName, emailAddress,
calendarUserAddresses, autoSchedule, enabledForCalendaring,
memberGUIDs, proxyGUIDs, readOnlyProxyGUIDs,
):
@@ -836,6 +939,9 @@
guid = guid,
shortName = shortName,
fullName = fullName,
+ firstName = firstName,
+ lastName = lastName,
+ emailAddress = emailAddress,
calendarUserAddresses = calendarUserAddresses,
autoSchedule = autoSchedule,
enabledForCalendaring = enabledForCalendaring,
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/directory.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/directory.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -138,7 +138,61 @@
for record in self.listRecords(recordType):
yield record
+ def recordsMatchingFields(self, fields, caseInsensitive=True, operand="or",
+ recordType=None):
+ # Default, bruteforce method; override with one optimized for each
+ # service
+ if recordType is None:
+ recordTypes = (
+ DirectoryService.recordType_users,
+ DirectoryService.recordType_groups,
+ DirectoryService.recordType_locations,
+ DirectoryService.recordType_resources,
+ )
+ else:
+ recordTypes = (recordType,)
+
+ def fieldMatches(fieldValue, value):
+ if caseInsensitive:
+ return fieldValue.lower().startswith(value.lower())
+ else:
+ return fieldValue.startswith(value)
+
+ def recordMatches(record):
+ if operand == "and":
+ for fieldName, value in fields:
+ try:
+ fieldValue = getattr(record, fieldName)
+ if not fieldMatches(fieldValue, value):
+ return False
+ except AttributeError:
+ # No property => no match
+ return False
+ # we hit on every property
+ return True
+ else: # "or"
+ for fieldName, value in fields:
+ try:
+ fieldValue = getattr(record, fieldName)
+ if fieldMatches(fieldValue, value):
+ return True
+ except AttributeError:
+ # No value
+ pass
+ # we didn't hit any
+ return False
+
+ try:
+ for recordType in recordTypes:
+ for record in self.listRecords(recordType):
+ if recordMatches(record):
+ yield record
+ except UnknownRecordTypeError:
+ # Skip this service since it doesn't understand this record type
+ pass
+
+
class DirectoryRecord(LoggingMixIn):
implements(IDirectoryRecord)
@@ -155,6 +209,7 @@
def __init__(
self, service, recordType, guid, shortName, fullName,
+ firstName, lastName, emailAddress,
calendarUserAddresses, autoSchedule, enabledForCalendaring=True,
):
assert service.realmName is not None
@@ -174,6 +229,9 @@
self.guid = guid
self.shortName = shortName
self.fullName = fullName
+ self.firstName = firstName
+ self.lastName = lastName
+ self.emailAddress = emailAddress
self.enabledForCalendaring = enabledForCalendaring
self.calendarUserAddresses = calendarUserAddresses
self.autoSchedule = autoSchedule
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/idirectory.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/idirectory.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/idirectory.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -72,6 +72,13 @@
addresses.
"""
+ def recordsMatchingFields(fields):
+ """
+ @return: a sequence of L{IDirectoryRecord}s which match the given
+ fields.
+ """
+
+
class IDirectoryRecord(Interface):
"""
Directory Record
@@ -81,6 +88,9 @@
guid = Attribute("The GUID of this record.")
shortName = Attribute("The name of this record.")
fullName = Attribute("The full name of this record.")
+ firstName = Attribute("The first name of this record.")
+ lastName = Attribute("The last name of this record.")
+ emailAddress = Attribute("The email address of this record.")
calendarUserAddresses = Attribute("A set of calendar user addresses for this record.")
autoSchedule = Attribute("Principal identified by this record should automatically accept/deny meetings.")
enabledForCalendaring = Attribute("Determines whether this record should be provisioned with a calendar home.")
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/principal.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/principal.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -125,6 +125,26 @@
def principalForCalendarUserAddress(self, address):
raise NotImplementedError("Subclass must implement principalForCalendarUserAddress()")
+ ##
+ # DAV-property-to-record-field mapping
+ ##
+
+ _cs_ns = "http://calendarserver.org/ns/"
+ _fieldMap = {
+ ("DAV:" , "displayname") : "fullName",
+ (_cs_ns, "first-name") : "firstName",
+ (_cs_ns, "last-name") : "lastName",
+ (_cs_ns, "email-address") : "emailAddress",
+ }
+
+ def propertyToField(self, property):
+ """
+ If property is a DAV property that maps to a directory field, return
+ that field's name, otherwise return None
+ """
+ return self._fieldMap.get(property.qname(), None)
+
+
class DirectoryPrincipalProvisioningResource (DirectoryProvisioningResource):
"""
Collection resource which provisions directory principals as its children.
@@ -212,6 +232,7 @@
return None
+
##
# Static
##
@@ -236,6 +257,7 @@
def principalCollections(self):
return (self,)
+
class DirectoryPrincipalTypeProvisioningResource (DirectoryProvisioningResource):
"""
Collection resource which provisions directory principals of a
@@ -289,6 +311,7 @@
def principalCollections(self):
return self.parent.principalCollections()
+
class DirectoryPrincipalUIDProvisioningResource (DirectoryProvisioningResource):
"""
Collection resource which provisions directory principals indexed
@@ -437,6 +460,9 @@
"""Record type: %s\n""" % (self.record.recordType,),
"""Short name: %s\n""" % (self.record.shortName,),
"""Full name: %s\n""" % (self.record.fullName,),
+ """First name: %s\n""" % (self.record.firstName,),
+ """Last name: %s\n""" % (self.record.lastName,),
+ """Email address: %s\n""" % (self.record.emailAddress,),
"""Principal UID: %s\n""" % (self.principalUID(),),
"""Principal URL: %s\n""" % (format_link(self.principalURL()),),
"""\nAlternate URIs:\n""" , format_list(format_link(u) for u in self.alternateURIs()),
@@ -597,6 +623,9 @@
"""Record type: %s\n""" % (self.record.recordType,),
"""Short name: %s\n""" % (self.record.shortName,),
"""Full name: %s\n""" % (self.record.fullName,),
+ """First name: %s\n""" % (self.record.firstName,),
+ """Last name: %s\n""" % (self.record.lastName,),
+ """Email address: %s\n""" % (self.record.emailAddress,),
"""Principal UID: %s\n""" % (self.principalUID(),),
"""Principal URL: %s\n""" % (format_link(self.principalURL()),),
"""\nAlternate URIs:\n""" , format_list(format_link(u) for u in self.alternateURIs()),
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/sqldb.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/sqldb.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/sqldb.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -350,6 +350,9 @@
guid = guid,
shortName = shortName,
fullName = name,
+ firstName = None,
+ lastName = None,
+ emailAddress = None,
calendarUserAddresses = calendarUserAddresses,
autoSchedule = autoSchedule,
)
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/sudo.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/sudo.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/sudo.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -132,6 +132,9 @@
guid=None,
shortName=shortName,
fullName=shortName,
+ firstName="",
+ lastName="",
+ emailAddress="",
calendarUserAddresses=set(),
autoSchedule=False,
enabledForCalendaring=False)
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/test/test_opendirectory.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/test/test_opendirectory.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/test/test_opendirectory.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -67,6 +67,9 @@
nodeName = "/LDAPv2/127.0.0.1",
shortName = "user",
fullName = "Some user",
+ firstName = "Some",
+ lastName = "User",
+ emailAddress = "someuser at example.com",
calendarUserAddresses = set(("mailtoguid at example.com",)),
autoSchedule = False,
enabledForCalendaring = True,
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/xmlaccountsparser.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/xmlaccountsparser.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/xmlaccountsparser.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -43,6 +43,9 @@
ELEMENT_GUID = "guid"
ELEMENT_PASSWORD = "password"
ELEMENT_NAME = "name"
+ELEMENT_FIRST_NAME = "first-name"
+ELEMENT_LAST_NAME = "last-name"
+ELEMENT_EMAIL_ADDRESS = "email-address"
ELEMENT_MEMBERS = "members"
ELEMENT_MEMBER = "member"
ELEMENT_CUADDR = "cuaddr"
@@ -163,6 +166,9 @@
self.guid = None
self.password = None
self.name = None
+ self.firstName = None
+ self.lastName = None
+ self.emailAddress = None
self.members = set()
self.groups = set()
self.calendarUserAddresses = set()
@@ -195,6 +201,18 @@
name = self.name % ctr
else:
name = self.name
+ if self.firstName and self.firstName.find("%") != -1:
+ firstName = self.firstName % ctr
+ else:
+ firstName = self.firstName
+ if self.lastName and self.lastName.find("%") != -1:
+ lastName = self.lastName % ctr
+ else:
+ lastName = self.lastName
+ if self.emailAddress and self.emailAddress.find("%") != -1:
+ emailAddress = self.emailAddress % ctr
+ else:
+ emailAddress = self.emailAddress
calendarUserAddresses = set()
for cuaddr in self.calendarUserAddresses:
if cuaddr.find("%") != -1:
@@ -207,6 +225,9 @@
result.guid = guid
result.password = password
result.name = name
+ result.firstName = firstName
+ result.lastName = lastName
+ result.emailAddress = emailAddress
result.members = self.members
result.calendarUserAddresses = calendarUserAddresses
result.autoSchedule = self.autoSchedule
@@ -237,6 +258,15 @@
elif child_name == ELEMENT_NAME:
if child.firstChild is not None:
self.name = child.firstChild.data.encode("utf-8")
+ elif child_name == ELEMENT_FIRST_NAME:
+ if child.firstChild is not None:
+ self.firstName = child.firstChild.data.encode("utf-8")
+ elif child_name == ELEMENT_LAST_NAME:
+ if child.firstChild is not None:
+ self.lastName = child.firstChild.data.encode("utf-8")
+ elif child_name == ELEMENT_EMAIL_ADDRESS:
+ if child.firstChild is not None:
+ self.emailAddress = child.firstChild.data.encode("utf-8")
elif child_name == ELEMENT_MEMBERS:
self._parseMembers(child, self.members)
elif child_name == ELEMENT_CUADDR:
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/xmlfile.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/directory/xmlfile.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -108,6 +108,9 @@
guid = xmlPrincipal.guid,
shortName = shortName,
fullName = xmlPrincipal.name,
+ firstName = xmlPrincipal.firstName,
+ lastName = xmlPrincipal.lastName,
+ emailAddress = xmlPrincipal.emailAddress,
calendarUserAddresses = xmlPrincipal.calendarUserAddresses,
autoSchedule = xmlPrincipal.autoSchedule,
enabledForCalendaring = xmlPrincipal.enabledForCalendaring,
Modified: CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/resource.py 2008-09-18 22:50:21 UTC (rev 3018)
+++ CalendarServer/branches/users/sagen/principal-property-search-3017/twistedcaldav/resource.py 2008-09-18 22:52:36 UTC (rev 3019)
@@ -705,6 +705,9 @@
(caldav_namespace, "calendar-user-address-set"),
(caldav_namespace, "schedule-inbox-URL" ),
(caldav_namespace, "schedule-outbox-URL" ),
+ (calendarserver_namespace, "first-name" ),
+ (calendarserver_namespace, "last-name" ),
+ (calendarserver_namespace, "email-address" ),
)
@classmethod
@@ -760,6 +763,28 @@
else:
return customxml.DropBoxHomeURL(davxml.HRef(url))
+ if name == "first-name":
+ firstName = self.record.firstName
+ if firstName:
+ return customxml.FirstNameProperty(firstName)
+ else:
+ return None
+
+ if name == "last-name":
+ lastName = self.record.lastName
+ if lastName:
+ return customxml.LastNameProperty(lastName)
+ else:
+ return None
+
+ if name == "email-address":
+ emailAddress = self.record.emailAddress
+ if emailAddress:
+ return customxml.EMailProperty(emailAddress)
+ else:
+ return None
+
+
return super(CalendarPrincipalResource, self).readProperty(property, request)
return maybeDeferred(defer)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080918/fde917ef/attachment-0001.html
More information about the calendarserver-changes
mailing list