[CalendarServer-changes] [11252] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Tue May 28 19:20:45 PDT 2013
Revision: 11252
http://trac.calendarserver.org//changeset/11252
Author: cdaboo at apple.com
Date: 2013-05-28 19:20:45 -0700 (Tue, 28 May 2013)
Log Message:
-----------
Fix open directory backer PROPFINDs. Cleanup after PyOD removal.
Modified Paths:
--------------
CalendarServer/trunk/conf/caldavd-apple.plist
CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py
CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/sharing.py
CalendarServer/trunk/twistedcaldav/stdconfig.py
Modified: CalendarServer/trunk/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-apple.plist 2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/conf/caldavd-apple.plist 2013-05-29 02:20:45 UTC (rev 11252)
@@ -534,9 +534,6 @@
<key>EnableSearchAddressBook</key>
<false/>
- <key>OpenDirectoryModule</key>
- <string>calendarserver.platform.darwin.od.opendirectory</string>
-
<key>Includes</key>
<array>
<string>/Library/Server/Calendar and Contacts/Config/caldavd-system.plist</string>
Modified: CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py 2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py 2013-05-29 02:20:45 UTC (rev 11252)
@@ -33,19 +33,17 @@
from twext.web2.auth.digest import DigestedCredentials
from twistedcaldav.config import config
-from twistedcaldav.directory.cachingdirectory import CachingDirectoryService,\
+from twistedcaldav.directory.cachingdirectory import CachingDirectoryService, \
CachingDirectoryRecord
from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
from twistedcaldav.directory.directory import DirectoryError, UnknownRecordTypeError
from twistedcaldav.directory.util import splitIntoBatches
from twistedcaldav.directory.principal import cuAddressConverter
-from calendarserver.platform.darwin.od import dsattributes, dsquery
-from twisted.python.reflect import namedModule
+from calendarserver.platform.darwin.od import opendirectory, dsattributes, dsquery
-
class OpenDirectoryService(CachingDirectoryService):
"""
OpenDirectory implementation of L{IDirectoryService}.
@@ -96,7 +94,7 @@
params['negativeCaching'])
if odModule is None:
- odModule = namedModule(config.OpenDirectoryModule)
+ odModule = opendirectory
self.odModule = odModule
try:
@@ -121,6 +119,7 @@
self.restrictToGUID = True
self.restrictedTimestamp = 0
+
@property
def restrictedGUIDs(self):
"""
@@ -140,7 +139,7 @@
dsattributes.eDSExact,
False,
dsattributes.kDSStdRecordTypeGroups,
- [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups,],
+ [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups, ],
))
results = self.odModule.queryRecordsWithAttribute_list(
self.directory,
@@ -149,7 +148,7 @@
dsattributes.eDSExact,
False,
dsattributes.kDSStdRecordTypeGroups,
- [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups,],
+ [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups, ],
)
if len(results) == 1:
@@ -166,6 +165,7 @@
# No restrictions
return None
+
def __cmp__(self, other):
if not isinstance(other, DirectoryRecord):
return super(DirectoryRecord, self).__eq__(other)
@@ -176,12 +176,14 @@
return diff
return 0
+
def __hash__(self):
h = hash(self.__class__.__name__)
for attr in ("node",):
h = (h + hash(getattr(self, attr))) & sys.maxint
return h
+
def _expandGroupMembership(self, members, nestedGroups, processedGUIDs=None, returnGroups=False):
if processedGUIDs is None:
@@ -240,9 +242,11 @@
):
yield GUID
+
def recordTypes(self):
return self._recordTypes
+
def listRecords(self, recordType):
"""
Retrieve all the records of recordType from the directory, but for
@@ -298,7 +302,6 @@
proxyGUIDs = ()
readOnlyProxyGUIDs = ()
- autoSchedule = False
if recordType in (
DirectoryService.recordType_resources,
@@ -310,7 +313,7 @@
resourceInfo = resourceInfo[0]
try:
(
- autoSchedule,
+ _ignore_autoSchedule,
proxy,
readOnlyProxy
) = self.parseResourceInfo(
@@ -345,20 +348,20 @@
nestedGUIDs = ()
record = OpenDirectoryRecord(
- service = self,
- recordType = recordType,
- guid = recordGUID,
- nodeName = "",
- shortNames = recordShortNames,
- authIDs = (),
- fullName = recordFullName,
- firstName = "",
- lastName = "",
- emailAddresses = "",
- memberGUIDs = memberGUIDs,
- nestedGUIDs = nestedGUIDs,
- extProxies = proxyGUIDs,
- extReadOnlyProxies = readOnlyProxyGUIDs,
+ service=self,
+ recordType=recordType,
+ guid=recordGUID,
+ nodeName="",
+ shortNames=recordShortNames,
+ authIDs=(),
+ fullName=recordFullName,
+ firstName="",
+ lastName="",
+ emailAddresses="",
+ memberGUIDs=memberGUIDs,
+ nestedGUIDs=nestedGUIDs,
+ extProxies=proxyGUIDs,
+ extReadOnlyProxies=readOnlyProxyGUIDs,
)
# (Copied from below)
@@ -369,7 +372,7 @@
if self.augmentService is not None:
d = self.augmentService.getAugmentRecord(record.guid,
recordType)
- d.addCallback(lambda x:record.addAugmentInformation(x))
+ d.addCallback(lambda x: record.addAugmentInformation(x))
records.append(record)
self.log_debug("ListRecords returning %d %s records" % (len(records),
@@ -453,9 +456,6 @@
return guids
-
-
-
_ODFields = {
'fullName' : {
'odField' : dsattributes.kDS1AttrDistinguishedName,
@@ -528,6 +528,7 @@
else:
return ()
+
def _setFromAttribute(self, attribute, lower=False):
if attribute:
if isinstance(attribute, str):
@@ -537,6 +538,7 @@
else:
return ()
+
def recordsMatchingTokens(self, tokens, context=None, lookupMethod=None):
"""
@param tokens: The tokens to search on
@@ -558,7 +560,7 @@
"""
if lookupMethod is None:
- lookupMethod=self.odModule.queryRecordsWithAttributes_list
+ lookupMethod = self.odModule.queryRecordsWithAttributes_list
def collectResults(results):
self.log_debug("Got back %d records from OD" % (len(results),))
@@ -620,20 +622,20 @@
# objects that are used to generate the REPORT result.
record = OpenDirectoryRecord(
- service = self,
- recordType = recordType,
- guid = recordGUID,
- nodeName = recordNodeName,
- shortNames = recordShortNames,
- authIDs = recordAuthIDs,
- fullName = recordFullName,
- firstName = recordFirstName,
- lastName = recordLastName,
- emailAddresses = recordEmailAddresses,
- memberGUIDs = memberGUIDs,
- nestedGUIDs = nestedGUIDs,
- extProxies = (),
- extReadOnlyProxies = (),
+ service=self,
+ recordType=recordType,
+ guid=recordGUID,
+ nodeName=recordNodeName,
+ shortNames=recordShortNames,
+ authIDs=recordAuthIDs,
+ fullName=recordFullName,
+ firstName=recordFirstName,
+ lastName=recordLastName,
+ emailAddresses=recordEmailAddresses,
+ memberGUIDs=memberGUIDs,
+ nestedGUIDs=nestedGUIDs,
+ extProxies=(),
+ extReadOnlyProxies=(),
)
# (Copied from below)
@@ -644,15 +646,16 @@
if self.augmentService is not None:
d = self.augmentService.getAugmentRecord(record.guid,
recordType)
- d.addCallback(lambda x:record.addAugmentInformation(x))
+ d.addCallback(lambda x: record.addAugmentInformation(x))
yield record
except KeyError:
pass
+
def multiQuery(directory, queries, recordTypes, attrs):
- byGUID = { }
+ byGUID = {}
sets = []
caseInsensitive = True
@@ -729,7 +732,7 @@
lookupMethod=None):
if lookupMethod is None:
- lookupMethod=self.odModule.queryRecordsWithAttribute_list
+ lookupMethod = self.odModule.queryRecordsWithAttribute_list
# Note that OD applies case-sensitivity globally across the entire
# query, not per expression, so the current code uses whatever is
@@ -795,20 +798,20 @@
# objects that are used to generate the REPORT result.
record = OpenDirectoryRecord(
- service = self,
- recordType = recordType,
- guid = recordGUID,
- nodeName = recordNodeName,
- shortNames = recordShortNames,
- authIDs = recordAuthIDs,
- fullName = recordFullName,
- firstName = recordFirstName,
- lastName = recordLastName,
- emailAddresses = recordEmailAddresses,
- memberGUIDs = memberGUIDs,
- nestedGUIDs = nestedGUIDs,
- extProxies = (),
- extReadOnlyProxies = (),
+ service=self,
+ recordType=recordType,
+ guid=recordGUID,
+ nodeName=recordNodeName,
+ shortNames=recordShortNames,
+ authIDs=recordAuthIDs,
+ fullName=recordFullName,
+ firstName=recordFirstName,
+ lastName=recordLastName,
+ emailAddresses=recordEmailAddresses,
+ memberGUIDs=memberGUIDs,
+ nestedGUIDs=nestedGUIDs,
+ extProxies=(),
+ extReadOnlyProxies=(),
)
# (Copied from below)
@@ -819,15 +822,16 @@
if self.augmentService is not None:
d = self.augmentService.getAugmentRecord(record.guid,
recordType)
- d.addCallback(lambda x:record.addAugmentInformation(x))
+ d.addCallback(lambda x: record.addAugmentInformation(x))
yield record
except KeyError:
pass
+
def multiQuery(directory, queries, attrs, operand):
- byGUID = { }
+ byGUID = {}
sets = []
for query, recordTypes in queries.iteritems():
@@ -884,7 +888,6 @@
results.append((data[dsattributes.kDSNAttrRecordName], data))
return results
-
operand = (dsquery.expression.OR if operand == "or"
else dsquery.expression.AND)
@@ -922,7 +925,7 @@
lookupMethod=None):
if lookupMethod is None:
- lookupMethod=self.odModule.queryRecordsWithAttribute_list
+ lookupMethod = self.odModule.queryRecordsWithAttribute_list
origIndexKey = indexKey
if indexType == self.INDEX_TYPE_CUA:
@@ -983,9 +986,8 @@
else:
raise UnknownRecordTypeError("Unknown OpenDirectory record type: %s" % (recordType))
-
# Because we're getting transient OD error -14987, try 3 times:
- for i in xrange(3):
+ for _ignore in xrange(3):
try:
self.log_debug("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)" % (
self.directory,
@@ -1023,24 +1025,23 @@
self.log_debug("opendirectory.queryRecordsWithAttribute_list matched records: %s" % (len(results),))
-
enabledRecords = []
disabledRecords = []
for (recordShortName, value) in results:
# Now get useful record info.
- recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
- recordShortNames = self._uniqueTupleFromAttribute(value.get(dsattributes.kDSNAttrRecordName))
- recordType = value.get(dsattributes.kDSNAttrRecordType)
+ recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
+ recordShortNames = self._uniqueTupleFromAttribute(value.get(dsattributes.kDSNAttrRecordName))
+ recordType = value.get(dsattributes.kDSNAttrRecordType)
if isinstance(recordType, list):
recordType = recordType[0]
- recordAuthIDs = self._setFromAttribute(value.get(dsattributes.kDSNAttrAltSecurityIdentities))
- recordFullName = value.get(dsattributes.kDS1AttrDistinguishedName)
- recordFirstName = value.get(dsattributes.kDS1AttrFirstName)
- recordLastName = value.get(dsattributes.kDS1AttrLastName)
+ recordAuthIDs = self._setFromAttribute(value.get(dsattributes.kDSNAttrAltSecurityIdentities))
+ recordFullName = value.get(dsattributes.kDS1AttrDistinguishedName)
+ recordFirstName = value.get(dsattributes.kDS1AttrFirstName)
+ recordLastName = value.get(dsattributes.kDS1AttrLastName)
recordEmailAddresses = self._setFromAttribute(value.get(dsattributes.kDSNAttrEMailAddress), lower=True)
- recordNodeName = value.get(dsattributes.kDSNAttrMetaNodeLocation)
+ recordNodeName = value.get(dsattributes.kDSNAttrMetaNodeLocation)
if not recordType:
self.log_debug("Record (unknown)%s in node %s has no recordType; ignoring."
@@ -1069,7 +1070,6 @@
else:
unrestricted = True
-
# Special case for groups, which have members.
if recordType == self.recordType_groups:
memberGUIDs = value.get(dsattributes.kDSNAttrGroupMembers)
@@ -1107,20 +1107,20 @@
readOnlyProxyGUIDs = (read_only_proxy,)
record = OpenDirectoryRecord(
- service = self,
- recordType = recordType,
- guid = recordGUID,
- nodeName = recordNodeName,
- shortNames = recordShortNames,
- authIDs = recordAuthIDs,
- fullName = recordFullName,
- firstName = recordFirstName,
- lastName = recordLastName,
- emailAddresses = recordEmailAddresses,
- memberGUIDs = memberGUIDs,
- nestedGUIDs = nestedGUIDs,
- extProxies = proxyGUIDs,
- extReadOnlyProxies = readOnlyProxyGUIDs,
+ service=self,
+ recordType=recordType,
+ guid=recordGUID,
+ nodeName=recordNodeName,
+ shortNames=recordShortNames,
+ authIDs=recordAuthIDs,
+ fullName=recordFullName,
+ firstName=recordFirstName,
+ lastName=recordLastName,
+ emailAddresses=recordEmailAddresses,
+ memberGUIDs=memberGUIDs,
+ nestedGUIDs=nestedGUIDs,
+ extProxies=proxyGUIDs,
+ extReadOnlyProxies=readOnlyProxyGUIDs,
)
# Look up augment information
@@ -1129,7 +1129,7 @@
if self.augmentService is not None:
d = self.augmentService.getAugmentRecord(record.guid,
recordType)
- d.addCallback(lambda x:record.addAugmentInformation(x))
+ d.addCallback(lambda x: record.addAugmentInformation(x))
# Override based on ResourceInfo
if autoSchedule:
@@ -1277,6 +1277,8 @@
returnValue(recordsByGUID.values())
+
+
def buildQueries(recordTypes, fields, mapping):
"""
Determine how many queries need to be performed in order to work around opendirectory
@@ -1296,6 +1298,7 @@
return queries
+
def buildQueriesFromTokens(tokens, mapping):
"""
OD /Local doesn't support nested complex queries, so create a list of
@@ -1336,17 +1339,17 @@
extProxies, extReadOnlyProxies,
):
super(OpenDirectoryRecord, self).__init__(
- service = service,
- recordType = recordType,
- guid = guid,
- shortNames = shortNames,
- authIDs = authIDs,
- fullName = fullName,
- firstName = firstName,
- lastName = lastName,
- emailAddresses = emailAddresses,
- extProxies = extProxies,
- extReadOnlyProxies = extReadOnlyProxies,
+ service=service,
+ recordType=recordType,
+ guid=guid,
+ shortNames=shortNames,
+ authIDs=authIDs,
+ fullName=fullName,
+ firstName=firstName,
+ lastName=lastName,
+ emailAddresses=emailAddresses,
+ extProxies=extProxies,
+ extReadOnlyProxies=extReadOnlyProxies,
)
self.nodeName = nodeName
@@ -1371,6 +1374,7 @@
self.fullName
)
+
def members(self):
if self.recordType != self.service.recordType_groups:
return
@@ -1380,6 +1384,7 @@
if userRecord is not None:
yield userRecord
+
def groups(self):
if self._groupMembershipGUIDs is None:
self._groupMembershipGUIDs = self.service.groupsForGUID(self.guid)
@@ -1389,12 +1394,15 @@
if record:
yield record
+
def memberGUIDs(self):
return set(self._memberGUIDs)
+
def nestedGUIDs(self):
return set(self._nestedGUIDs)
+
def verifyCredentials(self, credentials):
if isinstance(credentials, UsernamePassword):
# Check cached password
@@ -1486,6 +1494,8 @@
return super(OpenDirectoryRecord, self).verifyCredentials(credentials)
+
+
class OpenDirectoryInitError(DirectoryError):
"""
OpenDirectory initialization error.
Modified: CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py 2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py 2013-05-29 02:20:45 UTC (rev 11252)
@@ -61,8 +61,7 @@
from xmlrpclib import datetime
-from calendarserver.platform.darwin.od import dsattributes, dsquery
-from twisted.python.reflect import namedModule
+from calendarserver.platform.darwin.od import opendirectory, dsattributes, dsquery
class OpenDirectoryBackingService(DirectoryService):
"""
@@ -70,34 +69,36 @@
"""
baseGUID = "BF07A1A2-5BB5-4A4D-A59A-67260EA7E143"
-
+
def __repr__(self):
- return "<%s %r>" % (self.__class__.__name__, self.realmName, )
+ return "<%s %r>" % (self.__class__.__name__, self.realmName,)
+
def __init__(self, params):
self._actuallyConfigure(**params)
+
def _actuallyConfigure(
- self, queryPeopleRecords=True,
- peopleNode = "/Search/Contacts",
- queryUserRecords=True,
- userNode = "/Search",
- maxDSQueryRecords = 0, # maximum number of records requested for any ds query
-
- queryDSLocal = False, #query in DSLocal -- debug
- dsLocalCacheTimeout = 30,
- ignoreSystemRecords = True,
-
- liveQuery = True, # query directory service as needed
- fakeETag = True, # eTag is not reliable if True
-
- cacheQuery = False,
- cacheTimeout=30, # cache timeout
-
+ self, queryPeopleRecords=True,
+ peopleNode="/Search/Contacts",
+ queryUserRecords=True,
+ userNode="/Search",
+ maxDSQueryRecords=0, # maximum number of records requested for any ds query
+
+ queryDSLocal=False, # query in DSLocal -- debug
+ dsLocalCacheTimeout=30,
+ ignoreSystemRecords=True,
+
+ liveQuery=True, # query directory service as needed
+ fakeETag=True, # eTag is not reliable if True
+
+ cacheQuery=False,
+ cacheTimeout=30, # cache timeout
+
addDSAttrXProperties=False, # add dsattributes to vcards as "X-" attributes
- standardizeSyntheticUIDs = False, # use simple synthetic UIDs --- good for testing
+ standardizeSyntheticUIDs=False, # use simple synthetic UIDs --- good for testing
appleInternalServer=False,
-
+
additionalAttributes=[],
allowedAttributes=[],
directoryBackedAddressBook=None
@@ -105,14 +106,14 @@
"""
@queryPeopleRecords: C{True} to query for People records
@queryUserRecords: C{True} to query for User records
- @maxDSQueryRecords: maximum number of (unfiltered) ds records retrieved before raising
+ @maxDSQueryRecords: maximum number of (unfiltered) ds records retrieved before raising
NumberOfMatchesWithinLimits exception or returning results
@dsLocalCacheTimeout: how log to keep cache of DSLocal records
@liveQuery: C{True} to query the directory as needed
@fakeETag: C{True} to use a fake eTag; allows ds queries with partial attributes
@cacheQuery: C{True} to query the directory and cache results
@cacheTimeout: if caching, the average cache timeout
- @standardizeSyntheticUIDs: C{True} when creating synthetic UID (==f(Node, Type, Record Name)),
+ @standardizeSyntheticUIDs: C{True} when creating synthetic UID (==f(Node, Type, Record Name)),
use a standard Node name. This allows testing with the same UID on different hosts
@allowedAttributes: list of DSAttributes that are used to create VCards
@@ -124,16 +125,14 @@
self.peopleNode = None
self.userDirectory = None
self.userNode = None
-
+
self.realmName = None # needed for super
- self.odModule = namedModule(config.OpenDirectoryModule)
-
if queryPeopleRecords or not queryUserRecords:
self.peopleNode = peopleNode
try:
- self.peopleDirectory = self.odModule.odInit(peopleNode)
- except self.odModule.ODError, e:
+ self.peopleDirectory = opendirectory.odInit(peopleNode)
+ except opendirectory.ODError, e:
self.log_error("Open Directory (node=%s) Initialization error: %s" % (peopleNode, e))
raise
self.realmName = peopleNode
@@ -145,16 +144,15 @@
else:
self.userNode = userNode
try:
- self.userDirectory = self.odModule.odInit(userNode)
- except self.odModule.ODError, e:
+ self.userDirectory = opendirectory.odInit(userNode)
+ except opendirectory.ODError, e:
self.log_error("Open Directory (node=%s) Initialization error: %s" % (userNode, e))
raise
if self.realmName:
self.realmName += "+" + userNode
else:
self.realmName = userNode
-
-
+
self.maxDSQueryRecords = maxDSQueryRecords
self.ignoreSystemRecords = ignoreSystemRecords
@@ -165,13 +163,13 @@
self.fakeETag = fakeETag
self.cacheQuery = cacheQuery
-
+
self.cacheTimeout = cacheTimeout if cacheTimeout > 0 else 30
-
+
self.addDSAttrXProperties = addDSAttrXProperties
self.standardizeSyntheticUIDs = standardizeSyntheticUIDs
self.appleInternalServer = appleInternalServer
-
+
self.additionalAttributes = additionalAttributes
# filter allows attributes, but make sure there are a minimum of attributes for functionality
if allowedAttributes:
@@ -182,38 +180,35 @@
VCardRecord.dsqueryAttributesForProperty.get("X-INTERNAL-REQUIRED")
)))
if (self.allowedDSQueryAttributes != VCardRecord.allDSQueryAttributes):
- self.log_info("Allowed DS query attributes = %r" % (self.allowedDSQueryAttributes, ))
+ self.log_info("Allowed DS query attributes = %r" % (self.allowedDSQueryAttributes,))
else:
self.allowedDSQueryAttributes = VCardRecord.allDSQueryAttributes
-
+
#self.returnedAttributes = VCardRecord.allDSQueryAttributes
self.returnedAttributes = self.allowedDSQueryAttributes
-
-
-
-
+
self._dsLocalRecords = []
self._nextDSLocalQueryTime = 0
-
+
# get this now once
hostname = getfqdn()
if hostname:
self.defaultNodeName = "/LDAPv3/" + hostname
else:
self.defaultNodeName = None
-
+
#cleanup
self._cleanupTime = time.time()
-
+
# file system locks
self._initLockPath = join(config.DocumentRoot, ".directory_address_book_create_lock")
self._createdLockPath = join(config.DocumentRoot, ".directory_address_book_created_lock")
self._updateLockPath = join(config.DocumentRoot, ".directory_address_book_update_lock")
self._tmpDirAddressBookLockPath = join(config.DocumentRoot, ".directory_address_book_tmpFolder_lock")
-
+
self._updateLock = MemcacheLock("OpenDirectoryBacker", self._updateLockPath)
- self._tmpDirAddressBookLock = MemcacheLock("OpenDirectoryBacker", self._tmpDirAddressBookLockPath)
-
+ self._tmpDirAddressBookLock = MemcacheLock("OpenDirectoryBacker", self._tmpDirAddressBookLockPath)
+
# optimization so we don't have to always get create lock
self._triedCreateLock = False
self._created = False
@@ -229,37 +224,39 @@
return diff
return 0
+
def __hash__(self):
h = hash(self.__class__.__name__)
for attr in ("node",):
h = (h + hash(getattr(self, attr))) & sys.maxint
return h
-
+
+
@inlineCallbacks
def available(self):
if not self._triedCreateLock:
- returnValue( False )
+ returnValue(False)
elif not self._created:
createdLock = MemcacheLock("OpenDirectoryBacker", self._createdLockPath)
self.log_debug("blocking on lock of: \"%s\")" % self._createdLockPath)
self._created = (yield createdLock.locked())
-
+
returnValue(self._created)
-
-
+
+
def updateLock(self):
return self._updateLock
-
+
@inlineCallbacks
def createCache(self):
"""
If caching, create the cache for the first time.
"""
-
+
if not self.liveQuery:
self.log_info("loading directory address book")
-
+
# get init lock
initLock = MemcacheLock("OpenDirectoryBacker", self._initLockPath, timeout=0)
self.log_debug("Attempt lock of: \"%s\")" % self._initLockPath)
@@ -269,19 +266,18 @@
gotCreateLock = True
except MemcacheLockTimeoutError:
pass
-
+
self._triedCreateLock = True
-
+
if gotCreateLock:
self.log_debug("Got lock!")
- yield self._refreshCache( flushCache=False, creating=True )
+ yield self._refreshCache(flushCache=False, creating=True)
else:
self.log_debug("Could not get lock - directory address book will be filled by peer")
-
-
+
@inlineCallbacks
- def _refreshCache(self, flushCache=False, creating=False, reschedule=True, query=None, attributes=None, keepLock=False, clear=False, maxRecords=0 ):
+ def _refreshCache(self, flushCache=False, creating=False, reschedule=True, query=None, attributes=None, keepLock=False, clear=False, maxRecords=0):
"""
refresh the cache.
"""
@@ -296,24 +292,24 @@
cacheTimeout = (cacheTimeout * random()) - (cacheTimeout / 2)
cacheTimeout += self.cacheTimeout * 60
reactor.callLater(cacheTimeout, self._refreshCache) #@UndefinedVariable
- self.log_info("Refresh directory address book in %d minutes %d seconds" % divmod(cacheTimeout, 60))
+ self.log_info("Refresh directory address book in %d minutes %d seconds" % divmod(cacheTimeout, 60))
def cleanupLater():
-
+
# try to cancel previous call if last clean up was less than 15 minutes ago
- if (time.time() - self._cleanupTime) < 15*60:
+ if (time.time() - self._cleanupTime) < 15 * 60:
try:
self._lastCleanupCall.cancel()
except:
pass
-
+
#
- # Add jitter/fuzz factor
+ # Add jitter/fuzz factor
#
nom = 120
- later = nom* (random() + .5)
+ later = nom * (random() + .5)
self._lastCleanupCall = reactor.callLater(later, removeTmpAddressBooks) #@UndefinedVariable
- self.log_info("Remove temporary directory address books in %d minutes %d seconds" % divmod(later, 60))
+ self.log_info("Remove temporary directory address books in %d minutes %d seconds" % divmod(later, 60))
def getTmpDirAndTmpFilePrefixSuffix():
@@ -325,16 +321,16 @@
else:
tmpDir = gettempdir()
prefix = "directoryAddressBook-"
-
+
return (tmpDir, prefix, ".tmp")
-
+
def makeTmpFilename():
tmpDir, prefix, suffix = getTmpDirAndTmpFilePrefixSuffix()
fd, fname = mkstemp(suffix=suffix, prefix=prefix, dir=tmpDir)
os.close(fd)
os.remove(fname)
return fname
-
+
@inlineCallbacks
def removeTmpAddressBooks():
self.log_info("Checking for temporary directory address books")
@@ -343,13 +339,13 @@
tmpDirLock = self._tmpDirAddressBookLock
self.log_debug("blocking on lock of: \"%s\")" % self._tmpDirAddressBookLockPath)
yield tmpDirLock.acquire()
-
+
try:
for name in listdir(tmpDir):
if name.startswith(prefix) and name.endswith(suffix):
try:
path = join(tmpDir, name)
- self.log_info("Deleting temporary directory address book at: %s" % path)
+ self.log_info("Deleting temporary directory address book at: %s" % path)
FilePath(path).remove()
self.log_debug("Done deleting")
except:
@@ -357,107 +353,104 @@
finally:
self.log_debug("unlocking: \"%s\")" % self._tmpDirAddressBookLockPath)
yield tmpDirLock.release()
-
+
self._cleanupTime = time.time()
-
updateLock = None
limited = False
try:
-
+
try:
# get the records
if clear:
records = {}
else:
records, limited = (yield self._getDirectoryRecords(query, attributes, maxRecords))
-
+
# calculate the hash
- # simple for now, could use MD5 digest if too many collisions
+ # simple for now, could use MD5 digest if too many collisions
newAddressBookCTag = customxml.GETCTag(str(hash(self.baseGUID + ":" + self.realmName + ":" + "".join(str(hash(records[key])) for key in records.keys()))))
-
+
# get the old hash
oldAddressBookCTag = ""
updateLock = self.updateLock()
self.log_debug("blocking on lock of: \"%s\")" % self._updateLockPath)
yield updateLock.acquire()
-
+
if not flushCache:
# get update lock
try:
oldAddressBookCTag = self.directoryBackedAddressBook.readDeadProperty((calendarserver_namespace, "getctag"))
except:
oldAddressBookCTag = ""
-
+
self.log_debug("Comparing {http://calendarserver.org/ns/}getctag: new = %s, old = %s" % (newAddressBookCTag, oldAddressBookCTag))
if str(newAddressBookCTag) != str(oldAddressBookCTag):
-
+
self.log_debug("unlocking: \"%s\")" % self._updateLockPath)
yield updateLock.release()
updateLock = None
-
if not keepLock:
self.log_debug("unlocking: \"%s\")" % self._updateLockPath)
yield updateLock.release()
updateLock = None
-
+
except:
cleanupLater()
if reschedule:
- refreshLater()
+ refreshLater()
raise
-
+
if creating:
createdLock = MemcacheLock("OpenDirectoryBacker", self._createdLockPath)
self.log_debug("blocking on lock of: \"%s\")" % self._createdLockPath)
yield createdLock.acquire()
-
+
cleanupLater()
if reschedule:
- refreshLater()
-
+ refreshLater()
+
except:
if updateLock:
yield updateLock.release()
raise
- returnValue( (updateLock, limited) )
+ returnValue((updateLock, limited))
-
def _getDSLocalRecords(self):
-
+
def generateDSLocalRecords():
-
+
records = {}
-
+
recordTypes = [dsattributes.kDSStdRecordTypePeople, dsattributes.kDSStdRecordTypeUsers, ]
try:
- localNodeDirectory = self.odModule.odInit("/Local/Default")
+ localNodeDirectory = opendirectory.odInit("/Local/Default")
self.log_debug("opendirectory.listAllRecordsWithAttributes_list(%r,%r,%r)" % (
"/DSLocal",
recordTypes,
self.returnedAttributes,
))
- results = list(self.odModule.listAllRecordsWithAttributes_list(
+ results = list(opendirectory.listAllRecordsWithAttributes_list(
localNodeDirectory,
recordTypes,
self.returnedAttributes,
))
- except self.odModule.ODError, ex:
+ except opendirectory.ODError, ex:
self.log_error("Open Directory (node=%s) error: %s" % ("/Local/Default", str(ex)))
raise
-
- self._dsLocalRecords = []
+
+ self._dsLocalRecords = []
for (recordShortName, value) in results: #@UnusedVariable
-
+
record = VCardRecord(self, value, "/Local/Default")
if self.ignoreSystemRecords:
# remove system users and people
if record.guid.startswith("FFFFEEEE-DDDD-CCCC-BBBB-AAAA"):
- self.log_info("Ignoring vcard for system record %s" % (record,))
+ self.log_info("Ignoring vcard for system record %s" % (record,))
continue
if record.guid in records:
@@ -469,79 +462,77 @@
traceback.print_exc()
self.log_info("Could not get vcard for record %s" % (record,))
else:
- self.log_debug("VCard text =\n%s" % (vCardText, ))
- records[record.guid] = record
-
+ self.log_debug("VCard text =\n%s" % (vCardText,))
+ records[record.guid] = record
+
return records
-
if not self.liveQuery or not self.queryDSLocal:
return {}
-
+
if time.time() > self._nextDSLocalQueryTime:
self._dsLocalRecords = generateDSLocalRecords()
- # Add jitter/fuzz factor
- self._nextDSLocalQueryTime = time.time() + self.dsLocalCacheTimeout * (random() + 0.5) * 60
+ # Add jitter/fuzz factor
+ self._nextDSLocalQueryTime = time.time() + self.dsLocalCacheTimeout * (random() + 0.5) * 60
return self._dsLocalRecords
-
+
@inlineCallbacks
- def _getDirectoryRecords(self, query=None, attributes=None, maxRecords=0 ):
+ def _getDirectoryRecords(self, query=None, attributes=None, maxRecords=0):
"""
Get a list of filtered VCardRecord for the given query with the given attributes.
query == None gets all records. attribute == None gets VCardRecord.allDSQueryAttributes
"""
limited = False
- queryResults = (yield self._queryDirectory(query, attributes, maxRecords ))
+ queryResults = (yield self._queryDirectory(query, attributes, maxRecords))
if maxRecords and len(queryResults) >= maxRecords:
limited = True
- self.log_debug("Directory address book record limit (= %d) reached." % (maxRecords, ))
+ self.log_debug("Directory address book record limit (= %d) reached." % (maxRecords,))
self.log_debug("Query done. Inspecting %s results" % len(queryResults))
records = self._getDSLocalRecords().copy()
self.log_debug("Adding %s DSLocal results" % len(records.keys()))
-
+
for (recordShortName, value) in queryResults: #@UnusedVariable
-
+
record = VCardRecord(self, value, self.defaultNodeName)
if self.ignoreSystemRecords:
# remove system users and people
if record.guid.startswith("FFFFEEEE-DDDD-CCCC-BBBB-AAAA"):
- self.log_info("Ignoring vcard for system record %s" % (record,))
+ self.log_info("Ignoring vcard for system record %s" % (record,))
continue
-
+
if record.guid in records:
self.log_info("Ignoring vcard for record due to conflict (duplicate uuid): %s" % (record,))
else:
- records[record.guid] = record
-
+ records[record.guid] = record
+
self.log_debug("After filtering, %s records (limited=%s)." % (len(records), limited))
- returnValue((records, limited, ))
+ returnValue((records, limited,))
- def _queryDirectory(self, query=None, attributes=None, maxRecords=0 ):
-
+ def _queryDirectory(self, query=None, attributes=None, maxRecords=0):
+
startTime = time.time()
-
if not attributes:
attributes = self.returnedAttributes
-
+
attributes = list(set(attributes + self.additionalAttributes)) # remove duplicates
-
+
directoryAndRecordTypes = []
if self.peopleDirectory == self.userDirectory:
# use single ds query if possible for best performance
- directoryAndRecordTypes.append( (self.peopleDirectory, self.peopleNode, (dsattributes.kDSStdRecordTypePeople, dsattributes.kDSStdRecordTypeUsers) ) )
+ directoryAndRecordTypes.append((self.peopleDirectory, self.peopleNode, (dsattributes.kDSStdRecordTypePeople, dsattributes.kDSStdRecordTypeUsers)))
else:
if self.peopleDirectory:
- directoryAndRecordTypes.append( (self.peopleDirectory, self.peopleNode, dsattributes.kDSStdRecordTypePeople) )
+ directoryAndRecordTypes.append((self.peopleDirectory, self.peopleNode, dsattributes.kDSStdRecordTypePeople))
if self.userDirectory:
- directoryAndRecordTypes.append( (self.userDirectory, self.userNode, dsattributes.kDSStdRecordTypeUsers) )
-
+ directoryAndRecordTypes.append((self.userDirectory, self.userNode, dsattributes.kDSStdRecordTypeUsers))
+
allResults = []
for directory, node, recordType in directoryAndRecordTypes:
try:
@@ -558,7 +549,7 @@
maxRecords,
))
results = list(
- self.odModule.queryRecordsWithAttribute_list(
+ opendirectory.queryRecordsWithAttribute_list(
directory,
query.attribute,
query.value,
@@ -578,7 +569,7 @@
maxRecords,
))
results = list(
- self.odModule.queryRecordsWithAttributes_list(
+ opendirectory.queryRecordsWithAttributes_list(
directory,
query.generate(),
False,
@@ -594,32 +585,32 @@
maxRecords,
))
results = list(
- self.odModule.listAllRecordsWithAttributes_list(
+ opendirectory.listAllRecordsWithAttributes_list(
directory,
recordType,
attributes,
maxRecords,
))
- except self.odModule.ODError, ex:
+ except opendirectory.ODError, ex:
self.log_error("Open Directory (node=%s) error: %s" % (self.realmName, str(ex)))
raise
-
+
allResults.extend(results)
-
+
if maxRecords:
maxRecords -= len(results)
if maxRecords <= 0:
break
-
- elaspedTime = time.time()-startTime
- self.log_info("Timing: Directory query: %.1f ms (%d records, %.2f records/sec)" % (elaspedTime*1000, len(allResults), len(allResults)/elaspedTime))
+ elaspedTime = time.time() - startTime
+ self.log_info("Timing: Directory query: %.1f ms (%d records, %.2f records/sec)" % (elaspedTime * 1000, len(allResults), len(allResults) / elaspedTime))
return succeed(allResults)
-
+
+
def _getDSFilter(self, addressBookFilter):
"""
Convert the supplied addressbook-query into an expression tree.
-
+
@param filter: the L{Filter} for the addressbook-query to convert.
@return: (needsAllRecords, espressionAttributes, expression) tuple
"""
@@ -629,13 +620,13 @@
#print("propFilterExpression")
"""
Create an expression for a single prop-filter element.
-
+
@param propFilter: the L{PropertyFilter} element.
@return: (needsAllRecords, espressionAttributes, expressions) tuple
"""
-
- def definedExpression( defined, allOf, filterName, constant, queryAttributes, allAttrStrings):
- if constant or filterName in ("N" , "FN", "UID", ):
+
+ def definedExpression(defined, allOf, filterName, constant, queryAttributes, allAttrStrings):
+ if constant or filterName in ("N" , "FN", "UID",):
return (defined, [], []) # all records have this property so no records do not have it
else:
matchList = list(set([dsquery.match(attrName, "", dsattributes.eDSStartsWith) for attrName in allAttrStrings]))
@@ -643,84 +634,83 @@
return andOrExpression(allOf, queryAttributes, matchList)
else:
if len(matchList) > 1:
- expr = dsquery.expression( dsquery.expression.OR, matchList )
+ expr = dsquery.expression(dsquery.expression.OR, matchList)
else:
expr = matchList
- return (False, queryAttributes, [dsquery.expression( dsquery.expression.NOT, expr),])
+ return (False, queryAttributes, [dsquery.expression(dsquery.expression.NOT, expr), ])
#end isNotDefinedExpression()
-
-
+
+
def andOrExpression(propFilterAllOf, queryAttributes, matchList):
#print("andOrExpression(propFilterAllOf=%r, queryAttributes%r, matchList%r)" % (propFilterAllOf, queryAttributes, matchList))
if propFilterAllOf and len(matchList):
# add OR expression because parent will AND
- return (False, queryAttributes, [dsquery.expression( dsquery.expression.OR, matchList),])
+ return (False, queryAttributes, [dsquery.expression(dsquery.expression.OR, matchList), ])
else:
return (False, queryAttributes, matchList)
#end andOrExpression()
-
-
+
+
# short circuit parameter filters
- def supportedParamter( filterName, paramFilters, propFilterAllOf ):
-
- def supported( paramFilterName, paramFilterDefined, params ):
+ def supportedParamter(filterName, paramFilters, propFilterAllOf):
+
+ def supported(paramFilterName, paramFilterDefined, params):
paramFilterName = paramFilterName.upper()
if len(params.keys()) and ((paramFilterName in params.keys()) != paramFilterDefined):
return False
if len(params[paramFilterName]) and str(paramFilter.qualifier).upper() not in params[paramFilterName]:
return False
- return True
+ return True
#end supported()
-
-
+
oneSupported = False
for paramFilter in paramFilters:
if filterName == "PHOTO":
- if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, { "ENCODING": ["B",], "TYPE": ["JPEG",], }):
+ if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"ENCODING": ["B", ], "TYPE": ["JPEG", ], }):
return not propFilterAllOf
oneSupported |= propFilterAllOf
elif filterName == "ADR":
- if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, { "TYPE": ["WORK", "PREF", "POSTAL", "PARCEL",], }):
+ if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"TYPE": ["WORK", "PREF", "POSTAL", "PARCEL", ], }):
return not propFilterAllOf
oneSupported |= propFilterAllOf
elif filterName == "LABEL":
- if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, { "TYPE": ["POSTAL", "PARCEL",]}):
+ if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"TYPE": ["POSTAL", "PARCEL", ]}):
return not propFilterAllOf
oneSupported |= propFilterAllOf
elif filterName == "TEL":
- if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, { "TYPE": [], }): # has params derived from ds attributes
+ if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"TYPE": [], }): # has params derived from ds attributes
return not propFilterAllOf
oneSupported |= propFilterAllOf
elif filterName == "EMAIL":
- if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, { "TYPE": [], }): # has params derived from ds attributes
+ if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"TYPE": [], }): # has params derived from ds attributes
return not propFilterAllOf
oneSupported |= propFilterAllOf
elif filterName == "URL":
- if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, {}):
+ if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {}):
return not propFilterAllOf
oneSupported |= propFilterAllOf
elif filterName == "KEY":
- if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, { "ENCODING": ["B",], "TYPE": ["PGPPUBILICKEY", "USERCERTIFICATE", "USERPKCS12DATA", "USERSMIMECERTIFICATE",] }):
+ if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"ENCODING": ["B", ], "TYPE": ["PGPPUBILICKEY", "USERCERTIFICATE", "USERPKCS12DATA", "USERSMIMECERTIFICATE", ]}):
return not propFilterAllOf
oneSupported |= propFilterAllOf
elif not filterName.startswith("X-"): #X- IMHandles X-ABRELATEDNAMES excepted, no other params are used
if propFilterAllOf == paramFilter.defined:
return not propFilterAllOf
oneSupported |= propFilterAllOf
-
+
if propFilterAllOf:
return True
else:
return oneSupported
#end supportedParamter()
-
-
- def textMatchElementExpression( propFilterAllOf, textMatchElement ):
-
- # pre process text match strings for ds query
- def getMatchStrings( propFilter, matchString ):
-
- if propFilter.filter_name in ("REV" , "BDAY", ):
+
+
+ def textMatchElementExpression(propFilterAllOf, textMatchElement):
+
+ # pre process text match strings for ds query
+ def getMatchStrings(propFilter, matchString):
+
+ if propFilter.filter_name in ("REV" , "BDAY",):
rawString = matchString
matchString = ""
for c in rawString:
@@ -728,32 +718,32 @@
matchString += c
elif propFilter.filter_name == "GEO":
matchString = ",".join(matchString.split(";"))
-
- if propFilter.filter_name in ("N" , "ADR", "ORG", ):
+
+ if propFilter.filter_name in ("N" , "ADR", "ORG",):
# for structured properties, change into multiple strings for ds query
if propFilter.filter_name == "ADR":
#split by newline and comma
- rawStrings = ",".join( matchString.split("\n") ).split(",")
+ rawStrings = ",".join(matchString.split("\n")).split(",")
else:
#split by space
rawStrings = matchString.split(" ")
-
+
# remove empty strings
matchStrings = []
for oneString in rawStrings:
if len(oneString):
- matchStrings += [oneString,]
+ matchStrings += [oneString, ]
return matchStrings
-
+
elif len(matchString):
- return [matchString,]
+ return [matchString, ]
else:
return []
# end getMatchStrings
-
+
if constant:
# do the match right now! Return either all or none.
- return( textMatchElement.test([constant,]), [], [] )
+ return(textMatchElement.test([constant, ]), [], [])
else:
matchStrings = getMatchStrings(propFilter, textMatchElement.text)
@@ -764,7 +754,7 @@
return definedExpression(False, propFilterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
# else fall through to attribute exists case below
else:
-
+
# special case UID's formed from node and record name
if propFilter.filter_name == "UID":
matchString = matchStrings[0]
@@ -772,62 +762,61 @@
if seperatorIndex > 1:
recordNameStart = seperatorIndex + len(VCardRecord.peopleUIDSeparator)
else:
- seperatorIndex = matchString.find(VCardRecord.userUIDSeparator)
+ seperatorIndex = matchString.find(VCardRecord.userUIDSeparator)
if seperatorIndex > 1:
recordNameStart = seperatorIndex + len(VCardRecord.userUIDSeparator)
else:
recordNameStart = sys.maxint
-
- if recordNameStart < len(matchString)-1:
+
+ if recordNameStart < len(matchString) - 1:
try:
recordNameQualifier = matchString[recordNameStart:].decode("base64").decode("utf8")
except Exception, e:
self.log_debug("Could not decode UID string %r in %r: %r" % (matchString[recordNameStart:], matchString, e,))
else:
if textMatchElement.negate:
- return (False, queryAttributes,
- [dsquery.expression(dsquery.expression.NOT, dsquery.match(dsattributes.kDSNAttrRecordName, recordNameQualifier, dsattributes.eDSExact)),]
+ return (False, queryAttributes,
+ [dsquery.expression(dsquery.expression.NOT, dsquery.match(dsattributes.kDSNAttrRecordName, recordNameQualifier, dsattributes.eDSExact)), ]
)
else:
- return (False, queryAttributes,
- [dsquery.match(dsattributes.kDSNAttrRecordName, recordNameQualifier, dsattributes.eDSExact),]
+ return (False, queryAttributes,
+ [dsquery.match(dsattributes.kDSNAttrRecordName, recordNameQualifier, dsattributes.eDSExact), ]
)
-
+
# use match_type where possible depending on property/attribute mapping
# Note that case sensitive negate will not work
# Should return all records in that case
matchType = dsattributes.eDSContains
- if propFilter.filter_name in ("NICKNAME" , "TITLE" , "NOTE" , "UID", "URL", "N", "ADR", "ORG", "REV", "LABEL", ):
+ if propFilter.filter_name in ("NICKNAME" , "TITLE" , "NOTE" , "UID", "URL", "N", "ADR", "ORG", "REV", "LABEL",):
if textMatchElement.match_type == "equals":
matchType = dsattributes.eDSExact
elif textMatchElement.match_type == "starts-with":
matchType = dsattributes.eDSStartsWith
elif textMatchElement.match_type == "ends-with":
matchType = dsattributes.eDSEndsWith
-
+
matchList = []
for matchString in matchStrings:
matchList += [dsquery.match(attrName, matchString, matchType) for attrName in stringAttrStrs]
-
+
matchList = list(set(matchList))
-
+
if textMatchElement.negate:
if len(matchList) > 1:
- expr = dsquery.expression( dsquery.expression.OR, matchList )
+ expr = dsquery.expression(dsquery.expression.OR, matchList)
else:
expr = matchList
- return (False, queryAttributes, [dsquery.expression( dsquery.expression.NOT, expr),])
+ return (False, queryAttributes, [dsquery.expression(dsquery.expression.NOT, expr), ])
else:
return andOrExpression(propFilterAllOf, queryAttributes, matchList)
-
+
# attribute exists search
return definedExpression(True, propFilterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
#end textMatchElementExpression()
-
-
- # get attribute strings from dsqueryAttributesForProperty list
+
+ # get attribute strings from dsqueryAttributesForProperty list
queryAttributes = list(set(VCardRecord.dsqueryAttributesForProperty.get(propFilter.filter_name, [])).intersection(set(self.allowedDSQueryAttributes)))
-
+
binaryAttrStrs = []
stringAttrStrs = []
for attr in queryAttributes:
@@ -836,33 +825,33 @@
else:
stringAttrStrs.append(attr)
allAttrStrings = stringAttrStrs + binaryAttrStrs
-
+
constant = VCardRecord.constantProperties.get(propFilter.filter_name)
- if not constant and not allAttrStrings:
+ if not constant and not allAttrStrings:
return (False, [], [])
-
+
if propFilter.qualifier and isinstance(propFilter.qualifier, addressbookqueryfilter.IsNotDefined):
return definedExpression(False, filterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
-
+
paramFilterElements = [paramFilterElement for paramFilterElement in propFilter.filters if isinstance(paramFilterElement, addressbookqueryfilter.ParameterFilter)]
textMatchElements = [textMatchElement for textMatchElement in propFilter.filters if isinstance(textMatchElement, addressbookqueryfilter.TextMatch)]
propFilterAllOf = propFilter.propfilter_test == "allof"
-
+
# handle parameter filter elements
if len(paramFilterElements) > 0:
- if supportedParamter(propFilter.filter_name, paramFilterElements, propFilterAllOf ):
+ if supportedParamter(propFilter.filter_name, paramFilterElements, propFilterAllOf):
if len(textMatchElements) == 0:
return definedExpression(True, filterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
else:
if propFilterAllOf:
return (False, [], [])
-
+
# handle text match elements
propFilterNeedsAllRecords = propFilterAllOf
propFilterAttributes = []
propFilterExpressionList = []
for textMatchElement in textMatchElements:
-
+
textMatchNeedsAllRecords, textMatchExpressionAttributes, textMatchExpression = textMatchElementExpression(propFilterAllOf, textMatchElement)
if propFilterAllOf:
propFilterNeedsAllRecords &= textMatchNeedsAllRecords
@@ -870,20 +859,19 @@
propFilterNeedsAllRecords |= textMatchNeedsAllRecords
propFilterAttributes += textMatchExpressionAttributes
propFilterExpressionList += textMatchExpression
-
if (len(propFilterExpressionList) > 1) and (filterAllOf != propFilterAllOf):
propFilterExpressions = [dsquery.expression(dsquery.expression.AND if propFilterAllOf else dsquery.expression.OR , list(set(propFilterExpressionList)))] # remove duplicates
else:
propFilterExpressions = list(set(propFilterExpressionList))
-
+
return (propFilterNeedsAllRecords, propFilterAttributes, propFilterExpressions)
#end propFilterExpression
#print("propFilterListQuery: filterAllOf=%r, propFilters=%r" % (filterAllOf, propFilters,))
"""
Create an expression for a list of prop-filter elements.
-
+
@param filterAllOf: the C{True} if parent filter test is "allof"
@param propFilters: the C{list} of L{ComponentFilter} elements.
@return: (needsAllRecords, espressionAttributes, expression) tuple
@@ -892,7 +880,7 @@
attributes = []
expressions = []
for propFilter in propFilters:
-
+
propNeedsAllRecords, propExpressionAttributes, propExpression = propFilterExpression(filterAllOf, propFilter)
if filterAllOf:
needsAllRecords &= propNeedsAllRecords
@@ -907,13 +895,12 @@
expr = expressions[0]
else:
expr = None
-
+
return (needsAllRecords, attributes, expr)
-
-
+
#print("_getDSFilter")
# Lets assume we have a valid filter from the outset
-
+
# Top-level filter contains zero or more prop-filters
if addressBookFilter:
filterAllOf = addressBookFilter.filter_test == "allof"
@@ -922,17 +909,16 @@
else:
return (filterAllOf, [], [])
else:
- return (False, [], [])
-
-
+ return (False, [], [])
- def _attributesForAddressBookQuery(self, addressBookQuery ):
-
- propertyNames = []
+
+ def _attributesForAddressBookQuery(self, addressBookQuery):
+
+ propertyNames = []
#print( "addressBookQuery.qname=%r" % addressBookQuery.qname)
if addressBookQuery.qname() == ("DAV:", "prop"):
-
- for property in addressBookQuery.children:
+
+ for property in addressBookQuery.children:
#print("property = %r" % property )
if isinstance(property, carddavxml.AddressData):
for addressProperty in property.children:
@@ -940,17 +926,16 @@
if isinstance(addressProperty, carddavxml.Property):
#print("Adding property %r", addressProperty.attributes["name"])
propertyNames.append(addressProperty.attributes["name"])
-
+
elif not self.fakeETag and property.qname() == ("DAV:", "getetag"):
# for a real etag == md5(vCard), we need all attributes
propertyNames = None
- break;
-
-
+ break
+
if not len(propertyNames):
#print("using all attributes")
return self.returnedAttributes
-
+
else:
propertyNames.append("X-INTERNAL-MINIMUM-VCARD-PROPERTIES") # these properties are required to make a vCard
queryAttributes = []
@@ -961,74 +946,72 @@
return list(set(queryAttributes).intersection(set(self.returnedAttributes)))
-
+
@inlineCallbacks
- def cacheVCardsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults ):
+ def cacheVCardsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults):
"""
Cache the vCards for a given addressBookFilder and addressBookQuery
"""
startTime = time.time()
#print("Timing: cacheVCardsForAddressBookQuery.starttime=%f" % startTime)
-
-
- allRecords, filterAttributes, dsFilter = self._getDSFilter( addressBookFilter );
+
+ allRecords, filterAttributes, dsFilter = self._getDSFilter(addressBookFilter)
#print("allRecords = %s, query = %s" % (allRecords, "None" if dsFilter is None else dsFilter.generate(),))
-
+
if allRecords:
dsFilter = None # None expression == all Records
clear = not allRecords and not dsFilter
-
+
#get unique list of requested attributes
if clear:
attributes = None
else:
- queryAttributes = self._attributesForAddressBookQuery( addressBookQuery )
+ queryAttributes = self._attributesForAddressBookQuery(addressBookQuery)
attributes = filterAttributes + queryAttributes
-
+
#calc maxRecords from passed in maxResults allowing extra for second stage filtering in caller
maxRecords = int(maxResults * 1.2)
if self.maxDSQueryRecords and maxRecords > self.maxDSQueryRecords:
maxRecords = self.maxDSQueryRecords
-
- updateLock, limited = (yield self._refreshCache(reschedule=False, query=dsFilter, attributes=attributes, keepLock=True, clear=clear, maxRecords=maxRecords ))
- elaspedTime = time.time()-startTime
- self.log_info("Timing: Cache fill: %.1f ms" % (elaspedTime*1000,))
-
+ updateLock, limited = (yield self._refreshCache(reschedule=False, query=dsFilter, attributes=attributes, keepLock=True, clear=clear, maxRecords=maxRecords))
+ elaspedTime = time.time() - startTime
+ self.log_info("Timing: Cache fill: %.1f ms" % (elaspedTime * 1000,))
+
returnValue((updateLock, limited))
@inlineCallbacks
- def vCardRecordsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults ):
+ def vCardRecordsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults):
"""
Get vCards for a given addressBookFilder and addressBookQuery
"""
-
- allRecords, filterAttributes, dsFilter = self._getDSFilter( addressBookFilter );
+
+ allRecords, filterAttributes, dsFilter = self._getDSFilter(addressBookFilter)
#print("allRecords = %s, query = %s" % (allRecords, "None" if dsFilter is None else dsFilter.generate(),))
-
+
# testing:
# allRecords = True
-
+
if allRecords:
dsFilter = None # None expression == all Records
clear = not allRecords and not dsFilter
-
+
queryRecords = []
limited = False
if not clear:
- queryAttributes = self._attributesForAddressBookQuery( addressBookQuery )
+ queryAttributes = self._attributesForAddressBookQuery(addressBookQuery)
attributes = filterAttributes + queryAttributes
-
+
#calc maxRecords from passed in maxResults allowing extra for second stage filtering in caller
maxRecords = int(maxResults * 1.2)
if self.maxDSQueryRecords and maxRecords > self.maxDSQueryRecords:
maxRecords = self.maxDSQueryRecords
records, limited = (yield self._getDirectoryRecords(dsFilter, attributes, maxRecords))
-
+
#filter out bad records --- should only happen during development
for record in records.values():
try:
@@ -1038,12 +1021,13 @@
self.log_info("Could not get vcard for record %s" % (record,))
else:
if not record.firstValueForAttribute(dsattributes.kDSNAttrMetaNodeLocation).startswith("/Local"):
- self.log_debug("VCard text =\n%s" % (vCardText, ))
+ self.log_debug("VCard text =\n%s" % (vCardText,))
queryRecords.append(record)
-
- returnValue((queryRecords, limited,))
+ returnValue((queryRecords, limited,))
+
+
class VCardRecord(DirectoryRecord, DAVPropertyMixIn):
"""
Open Directory implementation of L{IDirectoryRecord}.
@@ -1053,10 +1037,10 @@
# will be used to translate vCard queries to od queries
dsqueryAttributesForProperty = {
-
+
"FN" : [
- dsattributes.kDS1AttrFirstName,
- dsattributes.kDS1AttrLastName,
+ dsattributes.kDS1AttrFirstName,
+ dsattributes.kDS1AttrLastName,
dsattributes.kDS1AttrMiddleName,
dsattributes.kDSNAttrNamePrefix,
dsattributes.kDSNAttrNameSuffix,
@@ -1064,8 +1048,8 @@
dsattributes.kDSNAttrRecordName,
],
"N" : [
- dsattributes.kDS1AttrFirstName,
- dsattributes.kDS1AttrLastName,
+ dsattributes.kDS1AttrFirstName,
+ dsattributes.kDS1AttrLastName,
dsattributes.kDS1AttrMiddleName,
dsattributes.kDSNAttrNamePrefix,
dsattributes.kDSNAttrNameSuffix,
@@ -1150,26 +1134,26 @@
"X-AIM" : [
dsattributes.kDSNAttrIMHandle,
],
- "X-JABBER" : [
+ "X-JABBER" : [
dsattributes.kDSNAttrIMHandle,
],
- "X-MSN" : [
+ "X-MSN" : [
dsattributes.kDSNAttrIMHandle,
],
- "X-YAHOO" : [
+ "X-YAHOO" : [
dsattributes.kDSNAttrIMHandle,
],
- "X-ICQ" : [
+ "X-ICQ" : [
dsattributes.kDSNAttrIMHandle,
],
- "X-ABRELATEDNAMES" : [
+ "X-ABRELATEDNAMES" : [
dsattributes.kDSNAttrRelationships,
],
"X-INTERNAL-MINIMUM-VCARD-PROPERTIES" : [
dsattributes.kDS1AttrGeneratedUID,
dsattributes.kDSNAttrMetaNodeLocation,
- dsattributes.kDS1AttrFirstName,
- dsattributes.kDS1AttrLastName,
+ dsattributes.kDS1AttrFirstName,
+ dsattributes.kDS1AttrLastName,
dsattributes.kDS1AttrMiddleName,
dsattributes.kDSNAttrNamePrefix,
dsattributes.kDSNAttrNameSuffix,
@@ -1184,30 +1168,28 @@
dsattributes.kDSNAttrMetaNodeLocation,
dsattributes.kDS1AttrDistinguishedName,
dsattributes.kDSNAttrRecordName,
- dsattributes.kDS1AttrFirstName,
- dsattributes.kDS1AttrLastName,
+ dsattributes.kDS1AttrFirstName,
+ dsattributes.kDS1AttrLastName,
dsattributes.kDSNAttrRecordType,
],
-
+
}
-
allDSQueryAttributes = sorted(list(set([attr for lookupAttributes in dsqueryAttributesForProperty.values()
for attr in lookupAttributes])))
binaryDSAttributeStrs = [attr[0] for attr in allDSQueryAttributes
- if isinstance(attr, tuple) ]
+ if isinstance(attr, tuple)]
stringDSAttributeStrs = [attr for attr in allDSQueryAttributes
- if isinstance(attr, str) ]
+ if isinstance(attr, str)]
allDSAttributeStrs = stringDSAttributeStrs + binaryDSAttributeStrs
-
+
#peopleUIDSeparator = "-" + OpenDirectoryBackingService.baseGUID + "-"
userUIDSeparator = "-bf07a1a2-"
peopleUIDSeparator = "-cf07a1a2-"
-
constantProperties = {
# 3.6.3 PRODID Type Definition
"PRODID": vCardProductID,
@@ -1215,11 +1197,10 @@
"VERSION": "3.0",
}
-
+
def __init__(self, service, recordAttributes, defaultNodeName=None):
-
- self.log_debug("service=%s, attributes=%s" % (service, recordAttributes))
+ self.log_debug("service=%s, attributes=%s" % (service, recordAttributes))
#save off for debugging
if service.addDSAttrXProperties:
@@ -1230,7 +1211,7 @@
self._vCardText = None
self._uriName = None
self._hRef = None
-
+
self.attributes = {}
for key, values in recordAttributes.items():
if key in VCardRecord.stringDSAttributeStrs:
@@ -1240,20 +1221,20 @@
self.attributes[key] = removeControlChars(values).decode("utf8")
else:
self.attributes[key] = values
-
+
# fill in missing essential attributes used for filtering
fullName = self.firstValueForAttribute(dsattributes.kDS1AttrDistinguishedName)
if not fullName:
fullName = self.firstValueForAttribute(dsattributes.kDSNAttrRecordName)
self.attributes[dsattributes.kDS1AttrDistinguishedName] = fullName
-
+
node = self.firstValueForAttribute(dsattributes.kDSNAttrMetaNodeLocation)
-
+
# use a better node name -- makes better synthetic GUIDS
if not node or node == "/LDAPv3/127.0.0.1":
node = defaultNodeName if defaultNodeName else service.realmName
self.attributes[dsattributes.kDSNAttrMetaNodeLocation] = node
-
+
guid = self.firstValueForAttribute(dsattributes.kDS1AttrGeneratedUID)
if not guid:
if service.standardizeSyntheticUIDs:
@@ -1262,37 +1243,35 @@
nodeUUIDStr = "%x" % abs(hash(node))
nameUUIDStr = "".join(self.firstValueForAttribute(dsattributes.kDSNAttrRecordName).encode("utf8").encode("base64").split("\n"))
if self.firstValueForAttribute(dsattributes.kDSNAttrRecordType) != dsattributes.kDSStdRecordTypePeople:
- guid = VCardRecord.userUIDSeparator.join([nodeUUIDStr, nameUUIDStr,])
+ guid = VCardRecord.userUIDSeparator.join([nodeUUIDStr, nameUUIDStr, ])
else:
- guid = VCardRecord.peopleUIDSeparator.join([nodeUUIDStr, nameUUIDStr,])
+ guid = VCardRecord.peopleUIDSeparator.join([nodeUUIDStr, nameUUIDStr, ])
-
# since guid is used as file name, normalize so uid uniqueness == fine name uniqueness
#guid = "/".join(guid.split(":")).upper()
self.attributes[dsattributes.kDS1AttrGeneratedUID] = guid
-
+
if self.firstValueForAttribute(dsattributes.kDS1AttrLastName) == "99":
del self.attributes[dsattributes.kDS1AttrLastName]
-
+
if self.firstValueForAttribute(dsattributes.kDSNAttrRecordType) != dsattributes.kDSStdRecordTypePeople:
recordType = DirectoryService.recordType_users
else:
recordType = DirectoryService.recordType_people
-
+
super(VCardRecord, self).__init__(
- service = service,
- recordType = recordType,
- guid = guid,
- shortNames = tuple(self.valuesForAttribute(dsattributes.kDSNAttrRecordName)),
- fullName = fullName,
- firstName = self.firstValueForAttribute(dsattributes.kDS1AttrFirstName, None),
- lastName = self.firstValueForAttribute(dsattributes.kDS1AttrLastName, None),
- emailAddresses = (),
- calendarUserAddresses = (),
- autoSchedule = False,
- enabledForCalendaring = False,
+ service=service,
+ recordType=recordType,
+ guid=guid,
+ shortNames=tuple(self.valuesForAttribute(dsattributes.kDSNAttrRecordName)),
+ fullName=fullName,
+ firstName=self.firstValueForAttribute(dsattributes.kDS1AttrFirstName, None),
+ lastName=self.firstValueForAttribute(dsattributes.kDS1AttrLastName, None),
+ emailAddresses=(),
+ calendarUserAddresses=(),
+ autoSchedule=False,
+ enabledForCalendaring=False,
)
-
def __repr__(self):
@@ -1304,7 +1283,8 @@
self.shortNames,
self.fullName
)
-
+
+
def __hash__(self):
s = "".join([
"%s:%s" % (attribute, self.valuesForAttribute(attribute),)
@@ -1318,29 +1298,29 @@
self.fileName = self.baseFileName + "-" + str(self.renameCounter)
self.fileNameLower = self.fileName.lower()
"""
-
- def hasAttribute(self, attributeName ):
+
+ def hasAttribute(self, attributeName):
return self.valuesForAttribute(attributeName, None) is not None
- def valuesForAttribute(self, attributeName, default_values=[] ):
+ def valuesForAttribute(self, attributeName, default_values=[]):
values = self.attributes.get(attributeName)
if (values is None):
return default_values
elif not isinstance(values, list):
- values = [values, ]
-
+ values = [values, ]
+
# ds templates often return empty attribute values
# get rid of them here
- nonEmptyValues = [(value.encode("utf-8") if isinstance(value, unicode) else value) for value in values if len(value) > 0 ]
-
+ nonEmptyValues = [(value.encode("utf-8") if isinstance(value, unicode) else value) for value in values if len(value) > 0]
+
if len(nonEmptyValues) > 0:
return nonEmptyValues
else:
return default_values
-
- def firstValueForAttribute(self, attributeName, default_value="" ):
+
+ def firstValueForAttribute(self, attributeName, default_value=""):
values = self.attributes.get(attributeName)
if values is None:
return default_value
@@ -1349,32 +1329,32 @@
else:
return values.encode("utf_8") if isinstance(values, unicode) else values
- def joinedValuesForAttribute(self, attributeName, separator=",", default_string="" ):
+
+ def joinedValuesForAttribute(self, attributeName, separator=",", default_string=""):
values = self.valuesForAttribute(attributeName, None)
if not values:
return default_string
else:
return separator.join(values)
-
- def isoDateStringForDateAttribute(self, attributeName, default_string="" ):
+
+ def isoDateStringForDateAttribute(self, attributeName, default_string=""):
modDate = self.firstValueForAttribute(attributeName, default_string)
revDate = None
if modDate:
if len(modDate) >= len("YYYYMMDD") and modDate[:8].isdigit():
- revDate = "%s-%s-%s" % (modDate[:4],modDate[4:6],modDate[6:8], )
+ revDate = "%s-%s-%s" % (modDate[:4], modDate[4:6], modDate[6:8],)
if len(modDate) >= len("YYYYMMDDHHMMSS") and modDate[8:14].isdigit():
- revDate += "T%s:%s:%sZ" % (modDate[8:10],modDate[10:12],modDate[12:14], )
+ revDate += "T%s:%s:%sZ" % (modDate[8:10], modDate[10:12], modDate[12:14],)
return revDate
-
-
+
def vCard(self):
-
-
+
+
def generateVCard():
-
- def isUniqueProperty(vcard, newProperty, ignoreParams = None):
+
+ def isUniqueProperty(vcard, newProperty, ignoreParams=None):
existingProperties = vcard.properties(newProperty.name())
for existingProperty in existingProperties:
if ignoreParams:
@@ -1385,14 +1365,14 @@
return False
return True
- def addUniqueProperty(vcard, newProperty, ignoreParams = None, attrType = None, attrValue = None):
+ def addUniqueProperty(vcard, newProperty, ignoreParams=None, attrType=None, attrValue=None):
if isUniqueProperty(vcard, newProperty, ignoreParams):
vcard.addProperty(newProperty)
else:
if attrType and attrValue:
- self.log_info("Ignoring attribute %r with value %r in creating property %r. A duplicate property already exists." % (attrType, attrValue, newProperty, ))
-
- def addPropertyAndLabel(groupCount, label, propertyName, propertyValue, parameters = None ):
+ self.log_info("Ignoring attribute %r with value %r in creating property %r. A duplicate property already exists." % (attrType, attrValue, newProperty,))
+
+ def addPropertyAndLabel(groupCount, label, propertyName, propertyValue, parameters=None):
groupCount[0] += 1
groupPrefix = "item%d" % groupCount[0]
vcard.addProperty(Property(propertyName, propertyValue, params=parameters, group=groupPrefix))
@@ -1406,30 +1386,30 @@
# special case for Apple
if self.service.appleInternalServer and attrType == dsattributes.kDSNAttrIMHandle:
splitValue = attrValue.split("|")
- if len (splitValue) > 1:
+ if len(splitValue) > 1:
attrValue = splitValue[0]
colonIndex = attrValue.find(":")
- if (colonIndex > len(attrValue)-2):
+ if (colonIndex > len(attrValue) - 2):
raise ValueError("Nothing after colon.")
- propertyValue = attrValue[colonIndex+1:]
+ propertyValue = attrValue[colonIndex + 1:]
labelString = attrValue[:colonIndex] if colonIndex > 0 else defaultLabel
paramTypeString = labelString.upper()
-
+
# add PREF to first prop's parameters
- paramTypeStrings = [paramTypeString,]
+ paramTypeStrings = [paramTypeString, ]
if preferred and "PREF" != paramTypeString:
- paramTypeStrings += ["PREF",]
- parameters = { "TYPE": paramTypeStrings, }
+ paramTypeStrings += ["PREF", ]
+ parameters = {"TYPE": paramTypeStrings, }
- #special case for IMHandles which the param is the last part of the property like X-AIM or X-JABBER
+ #special case for IMHandles which the param is the last part of the property like X-AIM or X-JABBER
if propertyPrefix:
propertyName = propertyPrefix + paramTypeString
# only add label prop if needed
if paramTypeString in nolabelParamTypes:
- addUniqueProperty(vcard, Property(propertyName, attrValue[colonIndex+1:], params=parameters), None, attrValue, attrType)
+ addUniqueProperty(vcard, Property(propertyName, attrValue[colonIndex + 1:], params=parameters), None, attrValue, attrType)
else:
# use special localizable addressbook labels where possible
abLabelString = labelMap.get(labelString, labelString)
@@ -1441,88 +1421,85 @@
self.log_debug("addPropertiesAndLabelsForPrefixedAttribute(): groupCount=%r, propertyPrefix=%r, propertyName=%r, nolabelParamTypes=%r, labelMap=%r, attrType=%r" % (groupCount[0], propertyPrefix, propertyName, nolabelParamTypes, labelMap, attrType,))
self.log_error("addPropertiesAndLabelsForPrefixedAttribute(): Trouble parsing attribute %s, with value \"%s\". Error = %s" % (attrType, attrValue, e,))
-
#print("VCardRecord.vCard")
# create vCard
vcard = Component("VCARD")
groupCount = [0]
-
+
# add constant properties - properties that are the same regardless of the record attributes
for key, value in VCardRecord.constantProperties.items():
vcard.addProperty(Property(key, value))
-
+
# 3.1 IDENTIFICATION TYPES http://tools.ietf.org/html/rfc2426#section-3.1
# 3.1.1 FN Type Definition
# dsattributes.kDS1AttrDistinguishedName, # Users distinguished or real name
#
# full name is required but this is set in OpenDiretoryBackingRecord.__init__
#vcard.addProperty(Property("FN", self.firstValueForAttribute(dsattributes.kDS1AttrDistinguishedName)))
-
+
# 3.1.2 N Type Definition
# dsattributes.kDS1AttrFirstName, # Used for first name of user or person record.
# dsattributes.kDS1AttrLastName, # Used for the last name of user or person record.
# dsattributes.kDS1AttrMiddleName, # Used for the middle name of user or person record.
# dsattributes.kDSNAttrNameSuffix, # Represents the name suffix of a user or person.
# ie. Jr., Sr., etc.
- # Usually found in user or people records (kDSStdRecordTypeUsers or
+ # Usually found in user or people records (kDSStdRecordTypeUsers or
# dsattributes.kDSStdRecordTypePeople).
# dsattributes.kDSNAttrNamePrefix, # Represents the title prefix of a user or person.
# ie. Mr., Ms., Mrs., Dr., etc.
- # Usually found in user or people records (kDSStdRecordTypeUsers or
+ # Usually found in user or people records (kDSStdRecordTypeUsers or
# dsattributes.kDSStdRecordTypePeople).
-
+
# name is required, so make sure we have one
# vcard says: Each name attribute can be a string or a list of strings.
if not self.hasAttribute(dsattributes.kDS1AttrFirstName) and not self.hasAttribute(dsattributes.kDS1AttrLastName):
familyName = self.firstValueForAttribute(dsattributes.kDS1AttrDistinguishedName)
else:
familyName = self.valuesForAttribute(dsattributes.kDS1AttrLastName, "")
-
+
nameObject = N(
- first = self.valuesForAttribute(dsattributes.kDS1AttrFirstName, ""),
- last = familyName,
- middle = self.valuesForAttribute(dsattributes.kDS1AttrMiddleName, ""),
- prefix = self.valuesForAttribute(dsattributes.kDSNAttrNamePrefix, ""),
- suffix = self.valuesForAttribute(dsattributes.kDSNAttrNameSuffix, ""),
+ first=self.valuesForAttribute(dsattributes.kDS1AttrFirstName, ""),
+ last=familyName,
+ middle=self.valuesForAttribute(dsattributes.kDS1AttrMiddleName, ""),
+ prefix=self.valuesForAttribute(dsattributes.kDSNAttrNamePrefix, ""),
+ suffix=self.valuesForAttribute(dsattributes.kDSNAttrNameSuffix, ""),
)
vcard.addProperty(Property("N", nameObject))
-
+
# set full name to Name with contiguous spaces stripped
# it turns out that Address Book.app ignores FN and creates it fresh from N in ABRecord
# so no reason to have FN distinct from N
- vcard.addProperty(Property("FN", nameObject.getFullName() ))
-
+ vcard.addProperty(Property("FN", nameObject.getFullName()))
+
# 3.1.3 NICKNAME Type Definition
# dsattributes.kDSNAttrNickName, # Represents the nickname of a user or person.
- # Usually found in user or people records (kDSStdRecordTypeUsers or
+ # Usually found in user or people records (kDSStdRecordTypeUsers or
# dsattributes.kDSStdRecordTypePeople).
for nickname in self.valuesForAttribute(dsattributes.kDSNAttrNickName):
addUniqueProperty(vcard, Property("NICKNAME", nickname), None, dsattributes.kDSNAttrNickName, nickname)
-
+
# 3.1.4 PHOTO Type Definition
- # dsattributes.kDSNAttrJPEGPhoto, # Used to store binary picture data in JPEG format.
- # Usually found in user, people or group records (kDSStdRecordTypeUsers,
+ # dsattributes.kDSNAttrJPEGPhoto, # Used to store binary picture data in JPEG format.
+ # Usually found in user, people or group records (kDSStdRecordTypeUsers,
# dsattributes.kDSStdRecordTypePeople,dsattributes.kDSStdRecordTypeGroups).
- # pyOpenDirectory always returns binary-encoded string
-
+ # pyOpenDirectory always returns binary-encoded string
+
for photo in self.valuesForAttribute(dsattributes.kDSNAttrJPEGPhoto):
- addUniqueProperty(vcard, Property("PHOTO", photo, params={"ENCODING": ["b",], "TYPE": ["JPEG",],}), None, dsattributes.kDSNAttrJPEGPhoto, photo)
-
-
+ addUniqueProperty(vcard, Property("PHOTO", photo, params={"ENCODING": ["b", ], "TYPE": ["JPEG", ], }), None, dsattributes.kDSNAttrJPEGPhoto, photo)
+
# 3.1.5 BDAY Type Definition
# dsattributes.kDS1AttrBirthday, # Single-valued attribute that defines the user's birthday.
# Format is x.208 standard YYYYMMDDHHMMSSZ which we will require as GMT time.
# 012345678901234
-
+
birthdate = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrBirthday)
if birthdate:
vcard.addProperty(Property("BDAY", PyCalendarDateTime.parseText(birthdate, fullISO=True)))
-
-
+
# 3.2 Delivery Addressing Types http://tools.ietf.org/html/rfc2426#section-3.2
#
# 3.2.1 ADR Type Definition
-
+
#address
# vcard says: Each address attribute can be a string or a list of strings.
extended = self.valuesForAttribute(dsattributes.kDSNAttrBuilding, "")
@@ -1531,37 +1508,36 @@
region = self.valuesForAttribute(dsattributes.kDSNAttrState, "")
code = self.valuesForAttribute(dsattributes.kDSNAttrPostalCode, "")
country = self.valuesForAttribute(dsattributes.kDSNAttrCountry, "")
-
+
if len(extended) > 0 or len(street) > 0 or len(city) > 0 or len(region) > 0 or len(code) > 0 or len(country) > 0:
vcard.addProperty(Property("ADR",
Adr(
#pobox = box,
- extended = extended,
- street = street,
- locality = city,
- region = region,
- postalcode = code,
- country = country,
+ extended=extended,
+ street=street,
+ locality=city,
+ region=region,
+ postalcode=code,
+ country=country,
),
- params = {"TYPE": ["WORK", "PREF", "POSTAL", "PARCEL",],}
+ params={"TYPE": ["WORK", "PREF", "POSTAL", "PARCEL", ], }
))
-
-
+
# 3.2.2 LABEL Type Definition
-
+
# dsattributes.kDSNAttrPostalAddress, # The postal address usually excluding postal code.
# dsattributes.kDSNAttrPostalAddressContacts, # multi-valued attribute that defines a record's alternate postal addresses .
# found in user records (kDSStdRecordTypeUsers) and resource records (kDSStdRecordTypeResources).
# dsattributes.kDSNAttrAddressLine1, # Line one of multiple lines of address data for a user.
# dsattributes.kDSNAttrAddressLine2, # Line two of multiple lines of address data for a user.
# dsattributes.kDSNAttrAddressLine3, # Line three of multiple lines of address data for a user.
-
+
for label in self.valuesForAttribute(dsattributes.kDSNAttrPostalAddress):
- addUniqueProperty(vcard, Property("LABEL", label, params={"TYPE": ["POSTAL", "PARCEL",]}), None, dsattributes.kDSNAttrPostalAddress, label)
-
+ addUniqueProperty(vcard, Property("LABEL", label, params={"TYPE": ["POSTAL", "PARCEL", ]}), None, dsattributes.kDSNAttrPostalAddress, label)
+
for label in self.valuesForAttribute(dsattributes.kDSNAttrPostalAddressContacts):
- addUniqueProperty(vcard, Property("LABEL", label, params={"TYPE": ["POSTAL", "PARCEL",]}), None, dsattributes.kDSNAttrPostalAddressContacts, label)
-
+ addUniqueProperty(vcard, Property("LABEL", label, params={"TYPE": ["POSTAL", "PARCEL", ]}), None, dsattributes.kDSNAttrPostalAddressContacts, label)
+
address = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine1)
addressLine2 = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine2)
if len(addressLine2) > 0:
@@ -1569,83 +1545,83 @@
addressLine3 = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine3)
if len(addressLine3) > 0:
address += "\n" + addressLine3
-
+
if len(address) > 0:
- vcard.addProperty(Property("LABEL", address, params={"TYPE": ["POSTAL", "PARCEL",]}))
-
+ vcard.addProperty(Property("LABEL", address, params={"TYPE": ["POSTAL", "PARCEL", ]}))
+
# 3.3 TELECOMMUNICATIONS ADDRESSING TYPES http://tools.ietf.org/html/rfc2426#section-3.3
# 3.3.1 TEL Type Definition
# TEL;TYPE=work,voice,pref,msg:+1-213-555-1234
-
+
# dsattributes.kDSNAttrPhoneNumber, # Telephone number of a user.
# dsattributes.kDSNAttrMobileNumber, # Represents the mobile numbers of a user or person.
- # Usually found in user or people records (kDSStdRecordTypeUsers or
+ # Usually found in user or people records (kDSStdRecordTypeUsers or
# dsattributes.kDSStdRecordTypePeople).
# dsattributes.kDSNAttrFaxNumber, # Represents the FAX numbers of a user or person.
- # Usually found in user or people records (kDSStdRecordTypeUsers or
+ # Usually found in user or people records (kDSStdRecordTypeUsers or
# kDSStdRecordTypePeople).
# dsattributes.kDSNAttrPagerNumber, # Represents the pager numbers of a user or person.
- # Usually found in user or people records (kDSStdRecordTypeUsers or
+ # Usually found in user or people records (kDSStdRecordTypeUsers or
# dsattributes.kDSStdRecordTypePeople).
# dsattributes.kDSNAttrHomePhoneNumber, # Home telephone number of a user or person.
# dsattributes.kDSNAttrPhoneContacts, # multi-valued attribute that defines a record's custom phone numbers .
- # found in user records (kDSStdRecordTypeUsers).
+ # found in user records (kDSStdRecordTypeUsers).
# Example: home fax:408-555-4444
-
- params = {"TYPE": ["WORK", "PREF", "VOICE",],}
+
+ params = {"TYPE": ["WORK", "PREF", "VOICE", ], }
for phone in self.valuesForAttribute(dsattributes.kDSNAttrPhoneNumber):
addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrPhoneNumber)
- params = {"TYPE": ["WORK", "VOICE",],}
-
- params = { "TYPE": ["WORK", "PREF", "CELL",], }
+ params = {"TYPE": ["WORK", "VOICE", ], }
+
+ params = {"TYPE": ["WORK", "PREF", "CELL", ], }
for phone in self.valuesForAttribute(dsattributes.kDSNAttrMobileNumber):
addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrMobileNumber)
- params = { "TYPE": ["WORK", "CELL",], }
-
- params = { "TYPE": ["WORK", "PREF", "FAX",], }
+ params = {"TYPE": ["WORK", "CELL", ], }
+
+ params = {"TYPE": ["WORK", "PREF", "FAX", ], }
for phone in self.valuesForAttribute(dsattributes.kDSNAttrFaxNumber):
addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrFaxNumber)
- params = { "TYPE": ["WORK", "FAX",], }
-
- params = { "TYPE": ["WORK", "PREF", "PAGER",], }
+ params = {"TYPE": ["WORK", "FAX", ], }
+
+ params = {"TYPE": ["WORK", "PREF", "PAGER", ], }
for phone in self.valuesForAttribute(dsattributes.kDSNAttrPagerNumber):
addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrPagerNumber)
- params = { "TYPE": ["WORK", "PAGER",], }
-
- params = { "TYPE": ["HOME", "PREF", "VOICE",], }
+ params = {"TYPE": ["WORK", "PAGER", ], }
+
+ params = {"TYPE": ["HOME", "PREF", "VOICE", ], }
for phone in self.valuesForAttribute(dsattributes.kDSNAttrHomePhoneNumber):
addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrHomePhoneNumber)
- params = { "TYPE": ["HOME", "VOICE",], }
-
+ params = {"TYPE": ["HOME", "VOICE", ], }
+
addPropertiesAndLabelsForPrefixedAttribute(groupCount, None, "TEL", "work",
- ["VOICE", "CELL", "FAX", "PAGER",], {},
- dsattributes.kDSNAttrPhoneContacts, )
+ ["VOICE", "CELL", "FAX", "PAGER", ], {},
+ dsattributes.kDSNAttrPhoneContacts,)
"""
# EXTEND: Use this attribute
# dsattributes.kDSNAttrAreaCode, # Area code of a user's phone number.
"""
-
+
# 3.3.2 EMAIL Type Definition
# dsattributes.kDSNAttrEMailAddress, # Email address of usually a user record.
-
+
# setup some params
- preferredWorkParams = { "TYPE": ["WORK", "PREF", "INTERNET",], }
- workParams = { "TYPE": ["WORK", "INTERNET",], }
+ preferredWorkParams = {"TYPE": ["WORK", "PREF", "INTERNET", ], }
+ workParams = {"TYPE": ["WORK", "INTERNET", ], }
params = preferredWorkParams
for emailAddress in self.valuesForAttribute(dsattributes.kDSNAttrEMailAddress):
addUniqueProperty(vcard, Property("EMAIL", emailAddress, params=params), (("TYPE", "PREF"),), emailAddress, dsattributes.kDSNAttrEMailAddress)
params = workParams
-
+
# dsattributes.kDSNAttrEMailContacts, # multi-valued attribute that defines a record's custom email addresses .
- # found in user records (kDSStdRecordTypeUsers).
+ # found in user records (kDSStdRecordTypeUsers).
# Example: home:johndoe at mymail.com
-
+
# check to see if parameters type are open ended. Could be any string
addPropertiesAndLabelsForPrefixedAttribute(groupCount, None, "EMAIL", "work",
- ["WORK", "HOME",], {},
- dsattributes.kDSNAttrEMailContacts, )
-
+ ["WORK", "HOME", ], {},
+ dsattributes.kDSNAttrEMailContacts,)
+
"""
# UNIMPLEMENTED:
# 3.3.3 MAILER Type Definition
@@ -1671,7 +1647,7 @@
# 3.5.1 TITLE Type Definition
for jobTitle in self.valuesForAttribute(dsattributes.kDSNAttrJobTitle):
addUniqueProperty(vcard, Property("TITLE", jobTitle), None, dsattributes.kDSNAttrJobTitle, jobTitle)
-
+
"""
# UNIMPLEMENTED:
# 3.5.2 ROLE Type Definition
@@ -1685,8 +1661,8 @@
department = self.joinedValuesForAttribute(dsattributes.kDSNAttrDepartment)
extra = self.joinedValuesForAttribute(dsattributes.kDSNAttrOrganizationInfo)
if len(company) > 0 or len(department) > 0:
- vcard.addProperty(Property("ORG", (company, department, extra, ),))
-
+ vcard.addProperty(Property("ORG", (company, department, extra,),))
+
# 3.6 EXPLANATORY TYPES http://tools.ietf.org/html/rfc2426#section-3.6
"""
# UNIMPLEMENTED:
@@ -1697,79 +1673,78 @@
# dsattributes.kDS1AttrNote, # Note attribute. Commonly used in printer records.
for comment in self.valuesForAttribute(dsattributes.kDS1AttrComment):
addUniqueProperty(vcard, Property("NOTE", comment), None, dsattributes.kDS1AttrComment, comment)
-
+
for note in self.valuesForAttribute(dsattributes.kDS1AttrNote):
addUniqueProperty(vcard, Property("NOTE", note), None, dsattributes.kDS1AttrNote, note)
-
+
# 3.6.3 PRODID Type Definition
#vcard.addProperty(Property("PRODID", vCardProductID + "//BUILD %s" % twistedcaldav.__version__))
#vcard.addProperty(Property("PRODID", vCardProductID))
# ADDED WITH CONTSTANT PROPERTIES
-
+
# 3.6.4 REV Type Definition
revDate = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrModificationTimestamp)
if revDate:
vcard.addProperty(Property("REV", PyCalendarDateTime.parseText(revDate, fullISO=True)))
-
+
"""
# UNIMPLEMENTED:
# 3.6.5 SORT-STRING Type Definition
# 3.6.6 SOUND Type Definition
"""
# 3.6.7 UID Type Definition
- # dsattributes.kDS1AttrGeneratedUID, # Used for 36 character (128 bit) unique ID. Usually found in user,
+ # dsattributes.kDS1AttrGeneratedUID, # Used for 36 character (128 bit) unique ID. Usually found in user,
# group, and computer records. An example value is "A579E95E-CDFE-4EBC-B7E7-F2158562170F".
# The standard format contains 32 hex characters and four hyphen characters.
# !! don't use self.guid which is URL encoded
vcard.addProperty(Property("UID", self.firstValueForAttribute(dsattributes.kDS1AttrGeneratedUID)))
-
- # 3.6.8 URL Type Definition
+
+ # 3.6.8 URL Type Definition
# dsattributes.kDSNAttrURL, # List of URLs.
# dsattributes.kDS1AttrWeblogURI, # Single-valued attribute that defines the URI of a user's weblog.
- # Usually found in user records (kDSStdRecordTypeUsers).
+ # Usually found in user records (kDSStdRecordTypeUsers).
# Example: http://example.com/blog/jsmith
for url in self.valuesForAttribute(dsattributes.kDS1AttrWeblogURI):
- addPropertyAndLabel(groupCount, "weblog", "URL", url, parameters = {"TYPE": ["Weblog",]})
-
+ addPropertyAndLabel(groupCount, "weblog", "URL", url, parameters={"TYPE": ["Weblog", ]})
+
for url in self.valuesForAttribute(dsattributes.kDSNAttrURL):
- addPropertyAndLabel(groupCount, "_$!<HomePage>!$_", "URL", url, parameters = {"TYPE": ["Homepage",]})
-
-
+ addPropertyAndLabel(groupCount, "_$!<HomePage>!$_", "URL", url, parameters={"TYPE": ["Homepage", ]})
+
# 3.6.9 VERSION Type Definition
# ALREADY ADDED
-
+
# 3.7 SECURITY TYPES http://tools.ietf.org/html/rfc2426#section-3.7
# 3.7.1 CLASS Type Definition
# ALREADY ADDED
-
+
# 3.7.2 KEY Type Definition
-
+
# dsattributes.kDSNAttrPGPPublicKey, # Pretty Good Privacy public encryption key.
# dsattributes.kDS1AttrUserCertificate, # Attribute containing the binary of the user's certificate.
# Usually found in user records. The certificate is data which identifies a user.
- # This data is attested to by a known party, and can be independently verified
+ # This data is attested to by a known party, and can be independently verified
# by a third party.
- # dsattributes.kDS1AttrUserPKCS12Data, # Attribute containing binary data in PKCS #12 format.
+ # dsattributes.kDS1AttrUserPKCS12Data, # Attribute containing binary data in PKCS #12 format.
# Usually found in user records. The value can contain keys, certificates,
# and other related information and is encrypted with a passphrase.
# dsattributes.kDS1AttrUserSMIMECertificate,# Attribute containing the binary of the user's SMIME certificate.
# Usually found in user records. The certificate is data which identifies a user.
- # This data is attested to by a known party, and can be independently verified
+ # This data is attested to by a known party, and can be independently verified
# by a third party. SMIME certificates are often used for signed or encrypted
# emails.
-
+
for key in self.valuesForAttribute(dsattributes.kDSNAttrPGPPublicKey):
- addUniqueProperty(vcard, Property("KEY", key, params = {"ENCODING": ["b",], "TYPE": ["PGPPublicKey",]}), None, dsattributes.kDSNAttrPGPPublicKey, key)
-
+ addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["PGPPublicKey", ]}), None, dsattributes.kDSNAttrPGPPublicKey, key)
+
for key in self.valuesForAttribute(dsattributes.kDS1AttrUserCertificate):
- addUniqueProperty(vcard, Property("KEY", key, params = {"ENCODING": ["b",], "TYPE": ["UserCertificate",]}), None, dsattributes.kDS1AttrUserCertificate, key)
-
+ addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["UserCertificate", ]}), None, dsattributes.kDS1AttrUserCertificate, key)
+
for key in self.valuesForAttribute(dsattributes.kDS1AttrUserPKCS12Data):
- addUniqueProperty(vcard, Property("KEY", key, params = {"ENCODING": ["b",], "TYPE": ["UserPKCS12Data",]}), None, dsattributes.kDS1AttrUserPKCS12Data, key)
-
+ addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["UserPKCS12Data", ]}), None, dsattributes.kDS1AttrUserPKCS12Data, key)
+
for key in self.valuesForAttribute(dsattributes.kDS1AttrUserSMIMECertificate):
- addUniqueProperty(vcard, Property("KEY", key, params = {"ENCODING": ["b",], "TYPE": ["UserSMIMECertificate",]}), None, dsattributes.kDS1AttrUserSMIMECertificate, key)
-
+ addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["UserSMIMECertificate", ]}), None, dsattributes.kDS1AttrUserSMIMECertificate, key)
+
"""
X- attributes, Address Book support
"""
@@ -1779,32 +1754,31 @@
# Values should be prefixed with the appropriate IM type
# ie. AIM:, Jabber:, MSN:, Yahoo:, or ICQ:
# Usually found in user records (kDSStdRecordTypeUsers).
-
+
addPropertiesAndLabelsForPrefixedAttribute(groupCount, "X-", None, "aim",
- ["AIM", "JABBER", "MSN", "YAHOO", "ICQ"],
- {},
+ ["AIM", "JABBER", "MSN", "YAHOO", "ICQ"],
+ {},
dsattributes.kDSNAttrIMHandle,)
-
+
# X-ABRELATEDNAMES
# dsattributes.kDSNAttrRelationships, # multi-valued attribute that defines the relationship to the record type .
- # found in user records (kDSStdRecordTypeUsers).
+ # found in user records (kDSStdRecordTypeUsers).
# Example: brother:John
addPropertiesAndLabelsForPrefixedAttribute(groupCount, None, "X-ABRELATEDNAMES", "friend",
- [],
- { "FATHER":"_$!<Father>!$_",
- "MOTHER":"_$!<Mother>!$_",
- "PARENT":"_$!<Parent>!$_",
- "BROTHER":"_$!<Brother>!$_",
- "SISTER":"_$!<Sister>!$_",
- "CHILD":"_$!<Child>!$_",
- "FRIEND":"_$!<Friend>!$_",
- "SPOUSE":"_$!<Spouse>!$_",
- "PARTNER":"_$!<Partner>!$_",
- "ASSISTANT":"_$!<Assistant>!$_",
- "MANAGER":"_$!<Manager>!$_", },
- dsattributes.kDSNAttrRelationships, )
-
-
+ [],
+ {"FATHER": "_$!<Father>!$_",
+ "MOTHER": "_$!<Mother>!$_",
+ "PARENT": "_$!<Parent>!$_",
+ "BROTHER": "_$!<Brother>!$_",
+ "SISTER": "_$!<Sister>!$_",
+ "CHILD": "_$!<Child>!$_",
+ "FRIEND": "_$!<Friend>!$_",
+ "SPOUSE": "_$!<Spouse>!$_",
+ "PARTNER": "_$!<Partner>!$_",
+ "ASSISTANT": "_$!<Assistant>!$_",
+ "MANAGER": "_$!<Manager>!$_", },
+ dsattributes.kDSNAttrRelationships,)
+
# special case for Apple
if self.service.appleInternalServer:
for manager in self.valuesForAttribute("dsAttrTypeNative:appleManager"):
@@ -1815,81 +1789,82 @@
managerValue = "%s %s" % (splitManager[0], splitManager[1])
else:
managerValue = manager
- addPropertyAndLabel( groupCount, "_$!<Manager>!$_", "X-ABRELATEDNAMES", managerValue, parameters={ "TYPE": ["Manager",]} )
-
+ addPropertyAndLabel(groupCount, "_$!<Manager>!$_", "X-ABRELATEDNAMES", managerValue, parameters={"TYPE": ["Manager", ]})
+
"""
# UNIMPLEMENTED: X- attributes
-
+
X-MAIDENNAME
X-PHONETIC-FIRST-NAME
X-PHONETIC-MIDDLE-NAME
X-PHONETIC-LAST-NAME
-
+
sattributes.kDS1AttrPicture, # Represents the path of the picture for each user displayed in the login window.
# Found in user records (kDSStdRecordTypeUsers).
-
+
dsattributes.kDS1AttrMapGUID, # Represents the GUID for a record's map.
dsattributes.kDSNAttrMapURI, # attribute that defines the URI of a user's location.
-
+
dsattributes.kDSNAttrOrganizationInfo, # Usually the organization info of a user.
dsattributes.kDSNAttrAreaCode, # Area code of a user's phone number.
-
- dsattributes.kDSNAttrMIME, # Data contained in this attribute type is a fully qualified MIME Type.
-
+
+ dsattributes.kDSNAttrMIME, # Data contained in this attribute type is a fully qualified MIME Type.
+
"""
-
+
# debug, create x attributes for all ds attributes
if self.service.addDSAttrXProperties:
for attribute in self.originalAttributes:
for value in self.valuesForAttribute(attribute):
- vcard.addProperty(Property("X-"+"-".join(attribute.split(":")), removeControlChars(value)))
-
+ vcard.addProperty(Property("X-" + "-".join(attribute.split(":")), removeControlChars(value)))
+
return vcard
-
if not self._vCard:
self._vCard = generateVCard()
-
+
return self._vCard
-
+
+
def vCardText(self):
if not self._vCardText:
self._vCardText = str(self.vCard())
-
+
return self._vCardText
+
def uriName(self):
if not self._uriName:
self._uriName = self.vCard().getProperty("UID").value() + ".vcf"
#print("uriName():self._uriName=%s" % self._uriName)
return self._uriName
-
-
+
+
def hRef(self, parentURI="/directory/"):
if not self._hRef:
self._hRef = davxml.HRef.fromString(joinURL(parentURI, self.uriName()))
-
+
return self._hRef
def readProperty(self, property, request):
-
+
if type(property) is tuple:
qname = property
else:
qname = property.qname()
namespace, name = qname
-
+
#print("VCardResource.readProperty: qname = %s" % (qname, ))
-
+
if namespace == dav_namespace:
if name == "resourcetype":
result = davxml.ResourceType.empty #@UndefinedVariable
#print("VCardResource.readProperty: qname = %s, result = %s" % (qname, result))
return result
elif name == "getetag":
- result = davxml.GETETag( ETag(hashlib.md5(self.vCardText()).hexdigest()).generate() )
+ result = davxml.GETETag(ETag(hashlib.md5(self.vCardText()).hexdigest()).generate())
#print("VCardResource.readProperty: qname = %s, result = %s" % (qname, result))
return result
elif name == "getcontenttype":
@@ -1942,14 +1917,15 @@
return self.directoryBackedAddressBook.readProperty(property, request)
+
def listProperties(self, request):
#print("VCardResource.listProperties()")
qnames = set(self.liveProperties())
# Add dynamic live properties that exist
dynamicLiveProperties = (
- (dav_namespace, "quota-available-bytes" ),
- (dav_namespace, "quota-used-bytes" ),
+ (dav_namespace, "quota-available-bytes"),
+ (dav_namespace, "quota-used-bytes"),
)
for dqname in dynamicLiveProperties:
#print("VCardResource.listProperties: removing dqname=%s" % (dqname,))
@@ -1963,22 +1939,22 @@
#for qn in qnames: print("VCardResource.listProperties: qn=%s" % (qn,))
yield qnames
-
+
listProperties = deferredGenerator(listProperties)
-
+
+
+
# utility
#remove control characters because vCard does not support them
-def removeControlChars( utf8String ):
+def removeControlChars(utf8String):
result = utf8String
for a in utf8String:
if '\x00' <= a <= '\x1F':
result = ""
for c in utf8String:
if '\x00' <= c <= '\x1F':
- pass
+ pass
else:
result += c
#if utf8String != result: print ("changed %r to %r" % (utf8String, result))
return result
-
-
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py 2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py 2013-05-29 02:20:45 UTC (rev 11252)
@@ -19,17 +19,17 @@
except ImportError:
pass
else:
+ from calendarserver.platform.darwin.od import dsattributes
from collections import defaultdict
- from twisted.trial.unittest import SkipTest
+ from twext.web2.auth.digest import DigestedCredentials
from twisted.internet.defer import inlineCallbacks
from twisted.python.runtime import platform
- from twext.web2.auth.digest import DigestedCredentials
- import twistedcaldav.directory.test.util
+ from twisted.trial.unittest import SkipTest
from twistedcaldav.directory import augment
- from twistedcaldav.directory.directory import DirectoryService
from twistedcaldav.directory.appleopendirectory import OpenDirectoryRecord
- from calendarserver.platform.darwin.od import dsattributes
+ from twistedcaldav.directory.directory import DirectoryService
from txdav.common.datastore.test.util import deriveValue, withSpecialValue
+ import twistedcaldav.directory.test.util
class DigestAuthModule(object):
"""
@@ -47,12 +47,13 @@
val = (response == self.response)
return val
+
# Wonky hack to prevent unclean reactor shutdowns
class DummyReactor(object):
@staticmethod
def callLater(*args):
pass
- import twistedcaldav.directory.appleopendirectory
+
twistedcaldav.directory.appleopendirectory.reactor = DummyReactor
class OpenDirectory (
@@ -89,20 +90,20 @@
def test_fullNameNone(self):
record = OpenDirectoryRecord(
- service = self.service(),
- recordType = DirectoryService.recordType_users,
- guid = "B1F93EB1-DA93-4772-9141-81C250DA36C2",
- nodeName = "/LDAPv2/127.0.0.1",
- shortNames = ("user",),
- authIDs = set(),
- fullName = None,
- firstName = "Some",
- lastName = "User",
- emailAddresses = set(("someuser at example.com",)),
- memberGUIDs = [],
- nestedGUIDs = [],
- extProxies = [],
- extReadOnlyProxies = [],
+ service=self.service(),
+ recordType=DirectoryService.recordType_users,
+ guid="B1F93EB1-DA93-4772-9141-81C250DA36C2",
+ nodeName="/LDAPv2/127.0.0.1",
+ shortNames=("user",),
+ authIDs=set(),
+ fullName=None,
+ firstName="Some",
+ lastName="User",
+ emailAddresses=set(("someuser at example.com",)),
+ memberGUIDs=[],
+ nestedGUIDs=[],
+ extProxies=[],
+ extReadOnlyProxies=[],
)
self.assertEquals(record.fullName, "")
@@ -110,20 +111,20 @@
@withSpecialValue("odModule", DigestAuthModule())
def test_invalidODDigest(self):
record = OpenDirectoryRecord(
- service = self.service(),
- recordType = DirectoryService.recordType_users,
- guid = "B1F93EB1-DA93-4772-9141-81C250DA35B3",
- nodeName = "/LDAPv2/127.0.0.1",
- shortNames = ("user",),
- authIDs = set(),
- fullName = "Some user",
- firstName = "Some",
- lastName = "User",
- emailAddresses = set(("someuser at example.com",)),
- memberGUIDs = [],
- nestedGUIDs = [],
- extProxies = [],
- extReadOnlyProxies = [],
+ service=self.service(),
+ recordType=DirectoryService.recordType_users,
+ guid="B1F93EB1-DA93-4772-9141-81C250DA35B3",
+ nodeName="/LDAPv2/127.0.0.1",
+ shortNames=("user",),
+ authIDs=set(),
+ fullName="Some user",
+ firstName="Some",
+ lastName="User",
+ emailAddresses=set(("someuser at example.com",)),
+ memberGUIDs=[],
+ nestedGUIDs=[],
+ extProxies=[],
+ extReadOnlyProxies=[],
)
digestFields = defaultdict(lambda: "...")
@@ -138,29 +139,29 @@
@withSpecialValue("odModule", DigestAuthModule())
def test_validODDigest(self):
record = OpenDirectoryRecord(
- service = self.service(),
- recordType = DirectoryService.recordType_users,
- guid = "B1F93EB1-DA93-4772-9141-81C250DA35B3",
- nodeName = "/LDAPv2/127.0.0.1",
- shortNames = ("user",),
- authIDs = set(),
- fullName = "Some user",
- firstName = "Some",
- lastName = "User",
- emailAddresses = set(("someuser at example.com",)),
- memberGUIDs = [],
- nestedGUIDs = [],
- extProxies = [],
- extReadOnlyProxies = [],
+ service=self.service(),
+ recordType=DirectoryService.recordType_users,
+ guid="B1F93EB1-DA93-4772-9141-81C250DA35B3",
+ nodeName="/LDAPv2/127.0.0.1",
+ shortNames=("user",),
+ authIDs=set(),
+ fullName="Some user",
+ firstName="Some",
+ lastName="User",
+ emailAddresses=set(("someuser at example.com",)),
+ memberGUIDs=[],
+ nestedGUIDs=[],
+ extProxies=[],
+ extReadOnlyProxies=[],
)
digestFields = {
- "username":"user",
- "realm":"/Search",
- "nonce":"ABC",
- "uri":"/",
- "response":"123",
- "algorithm":"md5",
+ "username": "user",
+ "realm": "/Search",
+ "nonce": "ABC",
+ "uri": "/",
+ "response": "123",
+ "algorithm": "md5",
}
od = deriveValue(self, "odModule", lambda self: None)
od.response = (
@@ -393,7 +394,6 @@
return results
-
#
# OR
#
@@ -428,7 +428,6 @@
results = list(results)
self.assertEquals(len(results), 1)
-
#
# AND
#
Modified: CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py 2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py 2013-05-29 02:20:45 UTC (rev 11252)
@@ -62,26 +62,27 @@
# uid = pwd.getpwnam(config.UserName)[2]
# gid = grp.getgrnam(config.GroupName)[2]
# os.chown(path, uid, gid)
-#
+#
# log.info("Created %s" % (path,))
-#
+#
# except (OSError,), e:
# # this is caused by multiprocessor race and is harmless
# if e.errno != errno.EEXIST:
# raise
-
+
def makeChild(self, name):
from twistedcaldav.simpleresource import SimpleCalDAVResource
return SimpleCalDAVResource(principalCollections=self.principalCollections())
+
def provisionDirectory(self):
if self.directory is None:
directoryClass = namedClass(config.DirectoryAddressBook.type)
-
+
log.info("Configuring: %s:%r"
% (config.DirectoryAddressBook.type, config.DirectoryAddressBook.params))
-
+
#add self as "directoryBackedAddressBook" parameter
params = config.DirectoryAddressBook.params.copy()
params["directoryBackedAddressBook"] = self
@@ -93,9 +94,9 @@
return succeed(None)
return self.directory.createCache()
-
+
#print ("DirectoryBackedAddressBookResource.provisionDirectory: provisioned")
-
+
return succeed(None)
@@ -120,6 +121,7 @@
),
)
+
def supportedReports(self):
result = super(DirectoryBackedAddressBookResource, self).supportedReports()
if config.EnableSyncReport:
@@ -127,9 +129,11 @@
result.remove(davxml.Report(davxml.SyncCollection(),))
return result
+
def resourceType(self):
return davxml.ResourceType.directory
+
def resourceID(self):
if self.directory:
resource_id = uuid.uuid5(uuid.UUID("5AAD67BF-86DD-42D7-9161-6AF977E4DAA3"), self.directory.baseGUID).urn
@@ -137,40 +141,44 @@
resource_id = "tag:unknown"
return resource_id
+
def isDirectoryBackedAddressBookCollection(self):
return True
+
def isAddressBookCollection(self):
#print( "DirectoryBackedAddressBookResource.isAddressBookCollection: return True" )
return True
+
def isCollection(self):
return True
+
def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
# Permissions here are fixed, and are not subject to inheritance rules, etc.
return succeed(self.defaultAccessControlList())
-
+
+
@inlineCallbacks
def renderHTTP(self, request):
if not self.directory:
- raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE,"Service is starting up" ))
+ raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, "Service is starting up"))
elif self.directory.liveQuery:
response = (yield maybeDeferred(super(DirectoryBackedAddressBookResource, self).renderHTTP, request))
returnValue(response)
else:
- available = (yield maybeDeferred(self.directory.available, ))
-
+ available = (yield maybeDeferred(self.directory.available,))
+
if not available:
- raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE,"Service is starting up" ))
+ raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, "Service is starting up"))
else:
updateLock = self.directory.updateLock()
yield updateLock.acquire()
try:
response = (yield maybeDeferred(super(DirectoryBackedAddressBookResource, self).renderHTTP, request))
-
+
finally:
yield updateLock.release()
-
+
returnValue(response)
-
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2013-05-29 02:20:45 UTC (rev 11252)
@@ -419,7 +419,7 @@
customxml.PubSubXMPPPushKeyProperty.qname(),
)
- if self.isAddressBookCollection():
+ if self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection():
baseProperties += (
element.ResourceID.qname(),
carddavxml.SupportedAddressData.qname(),
@@ -431,6 +431,12 @@
carddavxml.MaxResourceSize.qname(),
)
+ if self.isDirectoryBackedAddressBookCollection():
+ baseProperties += (
+ element.ResourceID.qname(),
+ carddavxml.SupportedAddressData.qname(),
+ )
+
if self.isNotificationCollection():
baseProperties += (
customxml.GETCTag.qname(),
@@ -444,7 +450,7 @@
if config.EnableSyncReport and (element.Report(element.SyncCollection(),) in self.supportedReports()):
baseProperties += (element.SyncToken.qname(),)
- if config.EnableAddMember and (self.isCalendarCollection() or self.isAddressBookCollection()):
+ if config.EnableAddMember and (self.isCalendarCollection() or self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection()):
baseProperties += (element.AddMember.qname(),)
if config.Sharing.Enabled:
@@ -455,7 +461,7 @@
customxml.SharedURL.qname(),
)
- elif config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection():
+ elif config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection():
baseProperties += (
customxml.Invite.qname(),
customxml.AllowedSharingModes.qname(),
@@ -525,16 +531,13 @@
else:
qname = property.qname()
- if self.isCalendarCollection() or self.isAddressBookCollection():
+ if self.isCalendarCollection() or (self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection()):
# Push notification DAV property "pushkey"
if qname == customxml.PubSubXMPPPushKeyProperty.qname():
- # FIXME: is there a better way to get back to the associated
- # datastore object?
- dataObject = getattr(self, "_newStoreObject")
- if dataObject is not None:
- notifier = dataObject.getNotifier("push")
+ if hasattr(self, "_newStoreObject"):
+ notifier = self._newStoreObject.getNotifier("push")
if notifier is not None:
propVal = customxml.PubSubXMPPPushKeyProperty(notifier.nodeName())
returnValue(propVal)
@@ -567,7 +570,7 @@
elif qname == customxml.GETCTag.qname() and (
self.isPseudoCalendarCollection() or
- self.isAddressBookCollection() or
+ self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection() or
self.isNotificationCollection()
):
returnValue(customxml.GETCTag.fromString((yield self.getInternalSyncToken())))
@@ -578,7 +581,7 @@
returnValue(element.SyncToken.fromString((yield self.getSyncToken())))
elif qname == element.AddMember.qname() and config.EnableAddMember and (
- self.isCalendarCollection() or self.isAddressBookCollection()
+ self.isCalendarCollection() or self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection()
):
url = (yield self.canonicalURL(request))
returnValue(element.AddMember(element.HRef.fromString(url + "/;add-member")))
@@ -631,7 +634,7 @@
}),
))
- elif qname == carddavxml.MaxResourceSize.qname() and self.isAddressBookCollection():
+ elif qname == carddavxml.MaxResourceSize.qname() and self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection():
# CardDAV, section 6.2.3
if config.MaxResourceSize:
returnValue(carddavxml.MaxResourceSize.fromString(
@@ -641,7 +644,7 @@
elif qname == customxml.Invite.qname():
if config.Sharing.Enabled and (
config.Sharing.Calendars.Enabled and self.isCalendarCollection() or
- config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection()
+ config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection()
):
result = (yield self.inviteProperty(request))
returnValue(result)
@@ -649,7 +652,7 @@
elif qname == customxml.AllowedSharingModes.qname():
if config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.isCalendarCollection():
returnValue(customxml.AllowedSharingModes(customxml.CanBeShared()))
- elif config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection():
+ elif config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection():
returnValue(customxml.AllowedSharingModes(customxml.CanBeShared()))
elif qname == customxml.SharedURL.qname():
@@ -1109,8 +1112,9 @@
def supportedReports(self):
result = super(CalDAVResource, self).supportedReports()
- result.append(element.Report(caldavxml.CalendarQuery(),))
- result.append(element.Report(caldavxml.CalendarMultiGet(),))
+ if config.EnableCalDAV:
+ result.append(element.Report(caldavxml.CalendarQuery(),))
+ result.append(element.Report(caldavxml.CalendarMultiGet(),))
if self.isCollection():
# Only allowed on collections
result.append(element.Report(caldavxml.FreeBusyQuery(),))
Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py 2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/sharing.py 2013-05-29 02:20:45 UTC (rev 11252)
@@ -239,7 +239,10 @@
"""
Return True if this is an owner shared calendar collection.
"""
- return self._newStoreObject.isShared() if self._newStoreObject else False
+ try:
+ return self._newStoreObject.isShared() if self._newStoreObject else False
+ except AttributeError:
+ return False
def setShare(self, share):
@@ -498,7 +501,7 @@
def uninviteUserToShare(self, userid, ace, request):
- """
+ """
Send out in uninvite first, and then remove this user from the share list.
"""
# Do not validate the userid - we want to allow invalid users to be removed because they
@@ -573,7 +576,7 @@
returnValue([])
#TODO: Cache
- if True: #not hasattr(self, "_invitations"):
+ if True: # not hasattr(self, "_invitations"):
acceptedHomeChildren = yield self._newStoreObject.asShared()
# remove direct shares (it might be OK not to remove these, but that would be different from legacy code)
@@ -582,7 +585,7 @@
invitedHomeChildren = (yield self._newStoreObject.asInvited()) + indirectAccceptedHomeChildren
self._invitations = sorted([Invitation(homeChild) for homeChild in invitedHomeChildren],
- key=lambda invitation:invitation.shareeUID())
+ key=lambda invitation: invitation.shareeUID())
returnValue(self._invitations)
@@ -612,7 +615,7 @@
@inlineCallbacks
- def inviteSingleUserToShare(self, userid, cn, ace, summary, request): #@UnusedVariable
+ def inviteSingleUserToShare(self, userid, cn, ace, summary, request): #@UnusedVariable
# We currently only handle local users
sharee = self.principalForCalendarUserAddress(userid)
@@ -637,7 +640,7 @@
@inlineCallbacks
- def uninviteSingleUserFromShare(self, userid, aces, request): #@UnusedVariable
+ def uninviteSingleUserFromShare(self, userid, aces, request): #@UnusedVariable
# Cancel invites - we'll just use whatever userid we are given
sharee = self.principalForCalendarUserAddress(userid)
@@ -682,7 +685,7 @@
returnValue(True)
- def inviteSingleUserUpdateToShare(self, userid, commonName, acesOLD, aceNEW, summary, request): #@UnusedVariable
+ def inviteSingleUserUpdateToShare(self, userid, commonName, acesOLD, aceNEW, summary, request): #@UnusedVariable
# Just update existing
return self.inviteSingleUserToShare(userid, commonName, aceNEW, summary, request)
@@ -1076,7 +1079,8 @@
if not request:
# FIXEME: Fake up a request that can be used to get the owner home resource
- class _FakeRequest(object):pass
+ class _FakeRequest(object):
+ pass
fakeRequest = _FakeRequest()
setattr(fakeRequest, TRANSACTION_KEY, self._newStoreHome._txn)
request = fakeRequest
@@ -1411,6 +1415,7 @@
return self.declineShare(request, hostUrl, replytoUID)
+
class Share(object):
"""
A L{Share} represents information about a collection which has been shared
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2013-05-29 02:20:45 UTC (rev 11252)
@@ -970,13 +970,6 @@
"FreeBusyIndexExpandMaxDays": 5 * 365,
"FreeBusyIndexDelayedExpand": True,
- # Specify which opendirectory module to use:
- # "opendirectory" is PyOpenDirectory (the old one which uses
- # DirectoryService.framework)
- # "calendarserver.platform.darwin.od.opendirectory" is the new PyObjC
- # version which uses OpenDirectory.framework
- "OpenDirectoryModule": "opendirectory",
-
# The RootResource uses a twext property store. Specify the class here
"RootResourcePropStoreClass": "twext.web2.dav.xattrprops.xattrPropertyStore",
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130528/aea2ed6d/attachment-0001.html>
More information about the calendarserver-changes
mailing list